Project

General

Profile

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

library / src / main / java / org / distorted / library / type / Dynamic.java @ c6dec65b

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 Statics.
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

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

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

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

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

    
102
  protected Vector<VectorNoise> vn;
103
  protected float[] mFactor;
104
  protected float[] mNoise;
105
  protected float[][] baseV;
106
  private float[] buf;
107
  private float[] old;
108

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

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

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

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

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

    
145
///////////////////////////////////////////////////////////////////////////////////////////////////
146

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

    
158
    baseV = new float[mDimension][mDimension];
159
    buf= new float[mDimension];
160
    old= new float[mDimension];
161
    }
162

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

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

    
208
///////////////////////////////////////////////////////////////////////////////////////////////////
209

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

    
218
    float t = d-index;
219
    t = t*t*(3-2*t);
220

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

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

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

    
243
                      return (upper-lower)*(d-index) + lower;
244
      }
245
    }
246

    
247
///////////////////////////////////////////////////////////////////////////////////////////////////
248
// debugging only
249

    
250
  private void printBase(String str)
251
    {
252
    String s;
253
    float t;
254

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

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

    
268
///////////////////////////////////////////////////////////////////////////////////////////////////
269
// debugging only
270

    
271
  private void checkBase()
272
    {
273
    float tmp;
274

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

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

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

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

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

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

    
301
///////////////////////////////////////////////////////////////////////////////////////////////////
302

    
303
  private void checkAngle(int index)
304
    {
305
    float cosA = 0.0f;
306

    
307
    for(int k=0;k<mDimension; k++)
308
      cosA += baseV[index][k]*old[k];
309

    
310
    if( cosA<0.0f )
311
      {
312
/*
313
      /// DEBUGGING ////
314
      String s = index+" (";
315
      float t;
316

    
317
      for(int j=0; j<mDimension; j++)
318
        {
319
        t = ((int)(100*baseV[index][j]))/(100.0f);
320
        s+=(" "+t);
321
        }
322
      s += ") (";
323

    
324
      for(int j=0; j<mDimension; j++)
325
        {
326
        t = ((int)(100*old[j]))/(100.0f);
327
        s+=(" "+t);
328
        }
329
      s+= ")";
330

    
331
      android.util.Log.e("dynamic", "kat: " + s);
332
      /// END DEBUGGING ///
333
*/
334
      for(int j=0; j<mDimension; j++)
335
        baseV[index][j] = -baseV[index][j];
336
      }
337
    }
338

    
339
///////////////////////////////////////////////////////////////////////////////////////////////////
340
// helper function in case we are interpolating through exactly 2 points
341

    
342
  protected void computeOrthonormalBase2(Static1D curr, Static1D next)
343
    {
344
    switch(mDimension)
345
      {
346
      case 1: baseV[0][0] = (next.x-curr.x);
347
              break;
348
      case 2: Static2D curr2 = (Static2D)curr;
349
              Static2D next2 = (Static2D)next;
350
              baseV[0][0] = (next2.x-curr2.x);
351
              baseV[0][1] = (next2.y-curr2.y);
352
              break;
353
      case 3: Static3D curr3 = (Static3D)curr;
354
              Static3D next3 = (Static3D)next;
355
              baseV[0][0] = (next3.x-curr3.x);
356
              baseV[0][1] = (next3.y-curr3.y);
357
              baseV[0][2] = (next3.z-curr3.z);
358
              break;
359
      case 4: Static4D curr4 = (Static4D)curr;
360
              Static4D next4 = (Static4D)next;
361
              baseV[0][0] = (next4.x-curr4.x);
362
              baseV[0][1] = (next4.y-curr4.y);
363
              baseV[0][2] = (next4.z-curr4.z);
364
              baseV[0][3] = (next4.w-curr4.w);
365
              break;
366
      case 5: Static5D curr5 = (Static5D)curr;
367
              Static5D next5 = (Static5D)next;
368
              baseV[0][0] = (next5.x-curr5.x);
369
              baseV[0][1] = (next5.y-curr5.y);
370
              baseV[0][2] = (next5.z-curr5.z);
371
              baseV[0][3] = (next5.w-curr5.w);
372
              baseV[0][4] = (next5.v-curr5.v);
373
              break;
374
      default: throw new RuntimeException("Unsupported dimension");
375
      }
376

    
377
    if( baseV[0][0] == 0.0f )
378
      {
379
      baseV[1][0] = 1.0f;
380
      baseV[1][1] = 0.0f;
381
      }
382
    else
383
      {
384
      baseV[1][0] = 0.0f;
385
      baseV[1][1] = 1.0f;
386
      }
387

    
388
    for(int i=2; i<mDimension; i++)
389
      {
390
      baseV[1][i] = 0.0f;
391
      }
392

    
393
    computeOrthonormalBase();
394
    }
395

    
396
///////////////////////////////////////////////////////////////////////////////////////////////////
397
// helper function in case we are interpolating through more than 2 points
398

    
399
  protected void computeOrthonormalBaseMore(float time,VectorCache vc)
400
    {
401
    for(int i=0; i<mDimension; i++)
402
      {
403
      baseV[0][i] = (3*vc.a[i]*time+2*vc.b[i])*time+vc.c[i];
404
      baseV[1][i] =  6*vc.a[i]*time+2*vc.b[i];
405
      }
406

    
407
    computeOrthonormalBase();
408
    }
409

    
410
///////////////////////////////////////////////////////////////////////////////////////////////////
411
// When this function gets called, baseV[0] and baseV[1] should have been filled with two mDimension-al
412
// vectors. This function then fills the rest of the baseV array with a mDimension-al Orthonormal base.
413
// (mDimension-2 vectors, pairwise orthogonal to each other and to the original 2). The function always
414
// leaves base[0] alone but generally speaking must adjust base[1] to make it orthogonal to base[0]!
415
// The whole baseV is then used to compute Noise.
416
//
417
// When computing noise of a point travelling along a N-dimensional path, there are three cases:
418
// a) we may be interpolating through 1 point, i.e. standing in place - nothing to do in this case
419
// b) we may be interpolating through 2 points, i.e. travelling along a straight line between them -
420
//    then pass the velocity vector in baseV[0] and anything linearly independent in base[1].
421
//    The output will then be discontinuous in dimensions>2 (sad corollary from the Hairy Ball Theorem)
422
//    but we don't care - we are travelling along a straight line, so velocity (aka baseV[0]!) does
423
//    not change.
424
// c) we may be interpolating through more than 2 points. Then interpolation formulas ensure the path
425
//    will never be a straight line, even locally -> we can pass in baseV[0] and baseV[1] the velocity
426
//    and the acceleration (first and second derivatives of the path) which are then guaranteed to be
427
//    linearly independent. Then we can ensure this is continuous in dimensions <=4. This leaves
428
//    dimension 5 (ATM WAVE is 5-dimensional) discontinuous -> WAVE will suffer from chaotic noise.
429
//
430
// Bear in mind here the 'normal' in 'orthonormal' means 'length equal to the length of the original
431
// velocity vector' (rather than the standard 1)
432

    
433
  protected void computeOrthonormalBase()
434
    {
435
    int non_zeros=0;
436
    int last_non_zero=-1;
437
    float value;
438
    for(int i=0; i<mDimension; i++)
439
      {
440
      value = baseV[0][i];
441

    
442
      if( value != 0.0f )
443
        {
444
        non_zeros++;
445
        last_non_zero=i;
446
        }
447
      }
448
                         // velocity is the 0 vector -> two consecutive points we are interpolating
449
    if( non_zeros==0 )   // through are identical -> no noise, set the base to 0 vectors.
450
      {
451
      for(int i=0; i<mDimension-1; i++)
452
        for(int j=0; j<mDimension; j++)
453
          baseV[i+1][j]= 0.0f;
454
      }
455
    else
456
      {
457
      // We can use (modified!) Gram-Schmidt.
458
      //
459
      // b[0] = b[0]
460
      // b[1] = b[1] - (<b[1],b[0]>/<b[0],b[0]>)*b[0]
461
      // b[2] = b[2] - (<b[2],b[0]>/<b[0],b[0]>)*b[0] - (<b[2],b[1]>/<b[1],b[1]>)*b[1]
462
      // 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]
463
      // (...)
464
      // then b[i] = b[i] / |b[i]|
465

    
466
      float tmp;
467

    
468
      for(int i=1; i<mDimension; i++)          /// one iteration computes baseV[i][*], the i-th orthonormal vector.
469
        {
470
        buf[i-1]=0.0f;
471

    
472
        for(int k=0; k<mDimension; k++)
473
          {
474
          old[k] = baseV[i][k];
475

    
476
          if( (i<=last_non_zero && k==i-1) || (i>=(last_non_zero+1) && k==i) )
477
            baseV[i][k]= baseV[0][last_non_zero];
478
          else
479
            baseV[i][k]= 0.0f;
480

    
481
          value = baseV[i-1][k];
482
          buf[i-1] += value*value;
483
          }
484

    
485
        for(int j=0; j<i; j++)
486
          {
487
          tmp = 0.0f;
488

    
489
          for(int k=0;k<mDimension; k++)
490
            {
491
            tmp += baseV[i][k]*baseV[j][k];
492
            }
493

    
494
          tmp /= buf[j];
495

    
496
          for(int k=0;k<mDimension; k++)
497
            {
498
            baseV[i][k] -= tmp*baseV[j][k];
499
            }
500
          }
501

    
502
        if( i>=2 ) checkAngle(i);
503
        }                                       /// end compute baseV[i][*]
504

    
505
      buf[mDimension-1]=0.0f;                   /// Normalize
506
      for(int k=0; k<mDimension; k++)           //
507
        {                                       //
508
        value = baseV[mDimension-1][k];         //
509
        buf[mDimension-1] += value*value;       //
510
        }                                       //
511
                                                //
512
      for(int i=1; i<mDimension; i++)           //
513
        {                                       //
514
        tmp = (float)Math.sqrt(buf[0]/buf[i]);  //
515
                                                //
516
        for(int k=0;k<mDimension; k++)          //
517
          {                                     //
518
          baseV[i][k] *= tmp;                   //
519
          }                                     //
520
        }                                       /// End Normalize
521
      }
522

    
523
    //printBase("end");
524
    //checkBase();
525
    }
526

    
527
///////////////////////////////////////////////////////////////////////////////////////////////////
528
// internal debugging only!
529
  
530
  public String print()
531
    {
532
    return "duration="+mDuration+" count="+mCount+" Noise="+mNoise+" numVectors="+numPoints+" mMode="+mMode;
533
    }
534

    
535
///////////////////////////////////////////////////////////////////////////////////////////////////
536

    
537
  abstract void interpolate(float[] buffer, int offset, float time);
538

    
539
///////////////////////////////////////////////////////////////////////////////////////////////////
540
// PUBLIC API
541
///////////////////////////////////////////////////////////////////////////////////////////////////
542

    
543
/**
544
 * Sets the mode of the interpolation to Loop, Path or Jump.
545
 * <ul>
546
 * <li>Loop is when we go from the first point all the way to the last, and the back to the first through 
547
 * the shortest way.
548
 * <li>Path is when we come back from the last point back to the first the same way we got there.
549
 * <li>Jump is when we go from first to last and then jump back to the first.
550
 * </ul>
551
 * 
552
 * @param mode {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
553
 */
554

    
555
  public void setMode(int mode)
556
    {
557
    mMode = mode;  
558
    }
559

    
560
///////////////////////////////////////////////////////////////////////////////////////////////////
561
/**
562
 * Returns the number of Statics this Dynamic has been fed with.
563
 *   
564
 * @return the number of Statics we are currently interpolating through.
565
 */
566
  public synchronized int getNumPoints()
567
    {
568
    return numPoints;  
569
    }
570

    
571
///////////////////////////////////////////////////////////////////////////////////////////////////
572
/**
573
 * Controls how many times we want to interpolate.
574
 * <p>
575
 * Count equal to 1 means 'go from the first Static to the last and back'. Does not have to be an
576
 * integer - i.e. count=1.5 would mean 'start at the first Point, go to the last, come back to the first, 
577
 * go to the last again and stop'.
578
 * Count<=0 means 'go on interpolating indefinitely'.
579
 * 
580
 * @param count the number of times we want to interpolate between our collection of Statics.
581
 */
582
  public void setCount(float count)
583
    {
584
    mCount = count;  
585
    }
586

    
587
///////////////////////////////////////////////////////////////////////////////////////////////////
588
/**
589
 * Sets the time it takes to do one full interpolation.
590
 * 
591
 * @param duration Time, in milliseconds, it takes to do one full interpolation, i.e. go from the first 
592
 *                 Point to the last and back. 
593
 */
594
  
595
  public void setDuration(long duration)
596
    {
597
    mDuration = duration;
598
    }
599

    
600
///////////////////////////////////////////////////////////////////////////////////////////////////
601
// end of DistortedInterpolator
602
  }
(6-6/17)