1 |
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1 |
///////////////////////////////////////////////////////////////////////////////////////////////////
|
2 |
2 |
// Copyright 2016 Leszek Koltunski leszek@koltunski.pl //
|
3 |
3 |
// //
|
4 |
4 |
// This file is part of Distorted. //
|
... | ... | |
18 |
18 |
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA //
|
19 |
19 |
///////////////////////////////////////////////////////////////////////////////////////////////////
|
20 |
20 |
|
21 |
|
package org.distorted.library.type;
|
22 |
|
|
23 |
|
import org.distorted.library.main.DistortedLibrary;
|
|
21 |
package org.distorted.library.type
|
24 |
22 |
|
25 |
|
import java.util.Random;
|
26 |
|
import java.util.Vector;
|
|
23 |
import org.distorted.library.main.DistortedLibrary
|
|
24 |
import java.util.Random
|
|
25 |
import java.util.Vector
|
|
26 |
import kotlin.math.sqrt
|
27 |
27 |
|
28 |
28 |
///////////////////////////////////////////////////////////////////////////////////////////////////
|
29 |
29 |
/** A class to interpolate between a list of Statics.
|
30 |
|
* <p><ul>
|
31 |
|
* <li>if there is only one Point, just return it.
|
32 |
|
* <li>if there are two Points, linearly bounce between them
|
33 |
|
* <li>if there are more, interpolate a path between them. Exact way we interpolate depends on the MODE.
|
34 |
|
* </ul>
|
|
30 |
*
|
|
31 |
* if there is only one Point, just return it.
|
|
32 |
* if there are two Points, linearly bounce between them
|
|
33 |
* if there are more, interpolate a path between them. Exact way we interpolate depends on the MODE.
|
|
34 |
*
|
35 |
35 |
*/
|
36 |
|
|
37 |
36 |
// The way Interpolation between more than 2 Points is done:
|
38 |
37 |
//
|
39 |
38 |
// Def: let V[i] = (V[i](x), V[i](y), V[i](z)) be the direction and speed (i.e. velocity) we have to
|
... | ... | |
56 |
55 |
//
|
57 |
56 |
// and similarly Y(t) and Z(t).
|
58 |
57 |
|
59 |
|
public abstract class Dynamic
|
60 |
|
{
|
61 |
|
/**
|
62 |
|
* Keep the speed of interpolation always changing. Time to cover one segment (distance between
|
63 |
|
* two consecutive points) always the same. Smoothly interpolate the speed between two segments.
|
64 |
|
*/
|
65 |
|
public static final int SPEED_MODE_SMOOTH = 0;
|
66 |
|
/**
|
67 |
|
* Make each segment have constant speed. Time to cover each segment is still the same, thus the
|
68 |
|
* speed will jump when passing through a point and then keep constant.
|
69 |
|
*/
|
70 |
|
public static final int SPEED_MODE_SEGMENT_CONSTANT = 1;
|
71 |
|
/**
|
72 |
|
* Have the speed be always, globally the same across all segments. Time to cover one segment will
|
73 |
|
* thus generally no longer be the same.
|
74 |
|
*/
|
75 |
|
public static final int SPEED_MODE_GLOBALLY_CONSTANT = 2; // TODO: not supported yet
|
76 |
|
|
77 |
|
/**
|
78 |
|
* One revolution takes us from the first point to the last and back to first through the shortest path.
|
79 |
|
*/
|
80 |
|
public static final int MODE_LOOP = 0;
|
81 |
|
/**
|
82 |
|
* One revolution takes us from the first point to the last and back to first through the same path.
|
83 |
|
*/
|
84 |
|
public static final int MODE_PATH = 1;
|
85 |
|
/**
|
86 |
|
* One revolution takes us from the first point to the last and jumps straight back to the first point.
|
87 |
|
*/
|
88 |
|
public static final int MODE_JUMP = 2;
|
89 |
|
|
90 |
|
/**
|
91 |
|
* The default mode of access. When in this mode, we are able to call interpolate() with points in time
|
92 |
|
* in any random order. This means one single Dynamic can be used in many effects simultaneously.
|
93 |
|
* On the other hand, when in this mode, it is not possible to smoothly interpolate when mDuration suddenly
|
94 |
|
* changes.
|
95 |
|
*/
|
96 |
|
public static final int ACCESS_TYPE_RANDOM = 0;
|
97 |
|
/**
|
98 |
|
* Set the mode to ACCESS_SEQUENTIAL if you need to change mDuration and you would rather have the Dynamic
|
99 |
|
* keep on smoothly interpolating.
|
100 |
|
* On the other hand, in this mode, a Dynamic can only be accessed in sequential manner, which means one
|
101 |
|
* Dynamic can only be used in one effect at a time.
|
102 |
|
*/
|
103 |
|
public static final int ACCESS_TYPE_SEQUENTIAL = 1;
|
104 |
|
|
105 |
|
protected int mDimension;
|
106 |
|
protected int numPoints;
|
107 |
|
protected int mSegment; // between which pair of points are we currently? (in case of PATH this is a bit complicated!)
|
108 |
|
protected boolean cacheDirty; // VectorCache not up to date
|
109 |
|
protected int mMode; // LOOP, PATH or JUMP
|
110 |
|
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
|
111 |
|
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.
|
112 |
|
protected double mLastPos;
|
113 |
|
protected int mAccessType;
|
114 |
|
protected int mSpeedMode;
|
115 |
|
protected float mTmpTime;
|
116 |
|
protected int mTmpVec, mTmpSeg;
|
117 |
|
|
118 |
|
protected class VectorNoise
|
119 |
|
{
|
120 |
|
float[][] n;
|
121 |
|
|
122 |
|
VectorNoise()
|
123 |
|
{
|
124 |
|
n = new float[mDimension][NUM_NOISE];
|
125 |
|
}
|
|
58 |
abstract class Dynamic
|
|
59 |
{
|
|
60 |
companion object
|
|
61 |
{
|
|
62 |
/**
|
|
63 |
* Keep the speed of interpolation always changing. Time to cover one segment (distance between
|
|
64 |
* two consecutive points) always the same. Smoothly interpolate the speed between two segments.
|
|
65 |
*/
|
|
66 |
const val SPEED_MODE_SMOOTH: Int = 0
|
|
67 |
|
|
68 |
/**
|
|
69 |
* Make each segment have constant speed. Time to cover each segment is still the same, thus the
|
|
70 |
* speed will jump when passing through a point and then keep constant.
|
|
71 |
*/
|
|
72 |
const val SPEED_MODE_SEGMENT_CONSTANT: Int = 1
|
|
73 |
|
|
74 |
/**
|
|
75 |
* Have the speed be always, globally the same across all segments. Time to cover one segment will
|
|
76 |
* thus generally no longer be the same.
|
|
77 |
*/
|
|
78 |
const val SPEED_MODE_GLOBALLY_CONSTANT: Int = 2 // TODO: not supported yet
|
|
79 |
|
|
80 |
/**
|
|
81 |
* One revolution takes us from the first point to the last and back to first through the shortest path.
|
|
82 |
*/
|
|
83 |
const val MODE_LOOP: Int = 0
|
|
84 |
|
|
85 |
/**
|
|
86 |
* One revolution takes us from the first point to the last and back to first through the same path.
|
|
87 |
*/
|
|
88 |
const val MODE_PATH: Int = 1
|
|
89 |
|
|
90 |
/**
|
|
91 |
* One revolution takes us from the first point to the last and jumps straight back to the first point.
|
|
92 |
*/
|
|
93 |
const val MODE_JUMP: Int = 2
|
|
94 |
|
|
95 |
/**
|
|
96 |
* The default mode of access. When in this mode, we are able to call interpolate() with points in time
|
|
97 |
* in any random order. This means one single Dynamic can be used in many effects simultaneously.
|
|
98 |
* On the other hand, when in this mode, it is not possible to smoothly interpolate when mDuration suddenly
|
|
99 |
* changes.
|
|
100 |
*/
|
|
101 |
const val ACCESS_TYPE_RANDOM: Int = 0
|
|
102 |
|
|
103 |
/**
|
|
104 |
* Set the mode to ACCESS_SEQUENTIAL if you need to change mDuration and you would rather have the Dynamic
|
|
105 |
* keep on smoothly interpolating.
|
|
106 |
* On the other hand, in this mode, a Dynamic can only be accessed in sequential manner, which means one
|
|
107 |
* Dynamic can only be used in one effect at a time.
|
|
108 |
*/
|
|
109 |
const val ACCESS_TYPE_SEQUENTIAL: Int = 1
|
|
110 |
|
|
111 |
private const val NUM_RATIO = 10 // we attempt to 'smooth out' the speed in each segment -
|
|
112 |
|
|
113 |
// remember this many 'points' inside the Cache for each segment.
|
|
114 |
protected val mTmpRatio: FloatArray = FloatArray(NUM_RATIO)
|
|
115 |
|
|
116 |
private val mRnd = Random()
|
|
117 |
private const val NUM_NOISE = 5 // used iff mNoise>0.0. Number of intermediary points between each pair of adjacent vectors
|
|
118 |
private var mPausedTime: Long = 0
|
|
119 |
|
|
120 |
@JvmStatic fun onPause() { mPausedTime = System.currentTimeMillis() }
|
|
121 |
}
|
|
122 |
|
|
123 |
var dimension: Int = 0
|
|
124 |
protected set
|
|
125 |
|
|
126 |
@get:Synchronized
|
|
127 |
var numPoints: Int = 0
|
|
128 |
protected set
|
|
129 |
protected var mSegment: Int = 0 // between which pair of points are we currently? (in case of PATH this is a bit complicated!)
|
|
130 |
protected var cacheDirty: Boolean = false // VectorCache not up to date
|
|
131 |
protected var mMode: Int = 0 // LOOP, PATH or JUMP
|
|
132 |
var duration: Long = 0 // number of milliseconds it takes to do a full loop/path from first vector to the last and back to the first
|
|
133 |
var count: Float = 0f // 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.
|
|
134 |
protected var mLastPos: Double = 0.0
|
|
135 |
protected var mAccessType: Int = 0
|
|
136 |
protected var mSpeedMode: Int = 0
|
|
137 |
protected var mTmpTime: Float = 0f
|
|
138 |
protected var mTmpVec: Int = 0
|
|
139 |
protected var mTmpSeg: Int = 0
|
|
140 |
|
|
141 |
var convexity: Float
|
|
142 |
get() = mConvexity
|
|
143 |
set(convexity)
|
|
144 |
{
|
|
145 |
if (mConvexity!=convexity)
|
|
146 |
{
|
|
147 |
mConvexity = convexity
|
|
148 |
cacheDirty = true
|
|
149 |
}
|
|
150 |
}
|
126 |
151 |
|
127 |
|
void computeNoise()
|
128 |
|
{
|
129 |
|
n[0][0] = mRnd.nextFloat();
|
130 |
|
for(int i=1; i<NUM_NOISE; i++) n[0][i] = n[0][i-1]+mRnd.nextFloat();
|
|
152 |
val speedMode: Float
|
|
153 |
get() = mSpeedMode.toFloat()
|
131 |
154 |
|
132 |
|
float sum = n[0][NUM_NOISE-1] + mRnd.nextFloat();
|
|
155 |
protected inner class VectorNoise
|
|
156 |
internal constructor()
|
|
157 |
{
|
|
158 |
var n: Array<FloatArray> = Array(dimension) { FloatArray(NUM_NOISE) }
|
133 |
159 |
|
134 |
|
for(int i=0; i<NUM_NOISE; i++)
|
|
160 |
fun computeNoise()
|
135 |
161 |
{
|
136 |
|
n[0][i] /=sum;
|
137 |
|
for(int j=1; j<mDimension; j++) n[j][i] = mRnd.nextFloat()-0.5f;
|
|
162 |
n[0][0] = mRnd.nextFloat()
|
|
163 |
for (i in 1..<NUM_NOISE) n[0][i] = n[0][i-1] + mRnd.nextFloat()
|
|
164 |
|
|
165 |
val sum = n[0][NUM_NOISE-1] + mRnd.nextFloat()
|
|
166 |
|
|
167 |
for (i in 0..<NUM_NOISE)
|
|
168 |
{
|
|
169 |
n[0][i] /= sum
|
|
170 |
for (j in 1..<dimension) n[j][i] = mRnd.nextFloat()-0.5f
|
|
171 |
}
|
138 |
172 |
}
|
139 |
|
}
|
140 |
173 |
}
|
141 |
174 |
|
142 |
|
protected Vector<VectorNoise> vn;
|
143 |
|
protected float[] mFactor;
|
144 |
|
protected float[] mNoise;
|
145 |
|
protected float[][] baseV;
|
|
175 |
protected var vn: Vector<VectorNoise>? = null
|
|
176 |
protected lateinit var mFactor: FloatArray
|
|
177 |
protected lateinit var mNoise: FloatArray
|
|
178 |
protected lateinit var baseV: Array<FloatArray>
|
146 |
179 |
|
147 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
148 |
|
// the coefficients of the X(t), Y(t) and Z(t) polynomials: X(t) = a[0]*T^3 + b[0]*T^2 + c[0]*t + d[0] etc.
|
149 |
|
// (velocity) is the velocity vector.
|
150 |
|
// (cached) is the original vector from vv (copied here so when interpolating we can see if it is
|
151 |
|
// still valid and if not - rebuild the Cache
|
152 |
|
|
153 |
|
protected class VectorCache
|
|
180 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
181 |
// the coefficients of the X(t), Y(t) and Z(t) polynomials: X(t) = a[0]*T^3 + b[0]*T^2 + c[0]*t + d[0] etc.
|
|
182 |
// (velocity) is the velocity vector.
|
|
183 |
// (cached) is the original vector from vv (copied here so when interpolating we can see if it is
|
|
184 |
// still valid and if not - rebuild the Cache
|
|
185 |
protected inner class VectorCache
|
|
186 |
internal constructor()
|
154 |
187 |
{
|
155 |
|
float[] a;
|
156 |
|
float[] b;
|
157 |
|
float[] c;
|
158 |
|
float[] d;
|
159 |
|
float[] velocity;
|
160 |
|
float[] cached;
|
161 |
|
float[] path_ratio;
|
162 |
|
|
163 |
|
VectorCache()
|
164 |
|
{
|
165 |
|
a = new float[mDimension];
|
166 |
|
b = new float[mDimension];
|
167 |
|
c = new float[mDimension];
|
168 |
|
d = new float[mDimension];
|
169 |
|
|
170 |
|
velocity = new float[mDimension];
|
171 |
|
cached = new float[mDimension];
|
172 |
|
path_ratio = new float[NUM_RATIO];
|
173 |
|
}
|
|
188 |
var a: FloatArray = FloatArray(dimension)
|
|
189 |
var b: FloatArray = FloatArray(dimension)
|
|
190 |
var c: FloatArray = FloatArray(dimension)
|
|
191 |
var d: FloatArray = FloatArray(dimension)
|
|
192 |
var velocity: FloatArray = FloatArray(dimension)
|
|
193 |
var cached: FloatArray = FloatArray(dimension)
|
|
194 |
var path_ratio: FloatArray = FloatArray(NUM_RATIO)
|
174 |
195 |
}
|
175 |
196 |
|
176 |
|
protected Vector<VectorCache> vc;
|
177 |
|
protected VectorCache tmpCache1, tmpCache2;
|
178 |
|
protected float mConvexity;
|
|
197 |
protected var vc: Vector<VectorCache>? = null
|
|
198 |
protected var mConvexity: Float = 0f
|
179 |
199 |
|
180 |
|
private static final int NUM_RATIO = 10; // we attempt to 'smooth out' the speed in each segment -
|
181 |
|
// remember this many 'points' inside the Cache for each segment.
|
|
200 |
private lateinit var buf: FloatArray
|
|
201 |
private lateinit var old: FloatArray
|
182 |
202 |
|
183 |
|
protected static final float[] mTmpRatio = new float[NUM_RATIO];
|
|
203 |
// where we randomize noise factors to make the way between the two vectors not so smooth.
|
|
204 |
private var mStartTime: Long = 0
|
|
205 |
private var mCorrectedTime: Long = 0
|
184 |
206 |
|
185 |
|
private float[] buf;
|
186 |
|
private float[] old;
|
187 |
|
private static final Random mRnd = new Random();
|
188 |
|
private static final int NUM_NOISE = 5; // used iff mNoise>0.0. Number of intermediary points between each pair of adjacent vectors
|
189 |
|
// where we randomize noise factors to make the way between the two vectors not so smooth.
|
190 |
|
private long mStartTime;
|
191 |
|
private long mCorrectedTime;
|
192 |
|
private static long mPausedTime;
|
|
207 |
protected constructor()
|
193 |
208 |
|
194 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
195 |
|
// hide this from Javadoc
|
196 |
|
|
197 |
|
protected Dynamic()
|
|
209 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
210 |
protected constructor(duration: Int, count: Float, dimension: Int)
|
198 |
211 |
{
|
|
212 |
vc = Vector()
|
|
213 |
vn = null
|
|
214 |
numPoints = 0
|
|
215 |
cacheDirty = false
|
|
216 |
mMode = MODE_LOOP
|
|
217 |
this.duration = duration.toLong()
|
|
218 |
this.count = count
|
|
219 |
this.dimension = dimension
|
|
220 |
mSegment = -1
|
|
221 |
mLastPos = -1.0
|
|
222 |
mAccessType = ACCESS_TYPE_RANDOM
|
|
223 |
mSpeedMode = SPEED_MODE_SMOOTH
|
|
224 |
mConvexity = 1.0f
|
|
225 |
mStartTime = -1
|
|
226 |
mCorrectedTime = 0
|
199 |
227 |
|
|
228 |
baseV = Array(this.dimension) { FloatArray(this.dimension) }
|
|
229 |
buf = FloatArray(this.dimension)
|
|
230 |
old = FloatArray(this.dimension)
|
200 |
231 |
}
|
201 |
232 |
|
202 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
203 |
|
|
204 |
|
protected Dynamic(int duration, float count, int dimension)
|
|
233 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
234 |
fun initDynamic()
|
205 |
235 |
{
|
206 |
|
vc = new Vector<>();
|
207 |
|
vn = null;
|
208 |
|
numPoints = 0;
|
209 |
|
cacheDirty = false;
|
210 |
|
mMode = MODE_LOOP;
|
211 |
|
mDuration = duration;
|
212 |
|
mCount = count;
|
213 |
|
mDimension = dimension;
|
214 |
|
mSegment = -1;
|
215 |
|
mLastPos = -1;
|
216 |
|
mAccessType= ACCESS_TYPE_RANDOM;
|
217 |
|
mSpeedMode = SPEED_MODE_SMOOTH;
|
218 |
|
mConvexity = 1.0f;
|
219 |
|
mStartTime = -1;
|
220 |
|
mCorrectedTime = 0;
|
221 |
|
|
222 |
|
baseV = new float[mDimension][mDimension];
|
223 |
|
buf = new float[mDimension];
|
224 |
|
old = new float[mDimension];
|
|
236 |
mStartTime = -1
|
|
237 |
mCorrectedTime = 0
|
225 |
238 |
}
|
226 |
239 |
|
227 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
228 |
|
|
229 |
|
void initDynamic()
|
|
240 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
241 |
protected fun computeSegmentAndTime(time: Float)
|
230 |
242 |
{
|
231 |
|
mStartTime = -1;
|
232 |
|
mCorrectedTime = 0;
|
|
243 |
when (mMode)
|
|
244 |
{
|
|
245 |
MODE_LOOP ->
|
|
246 |
{
|
|
247 |
mTmpTime= time*numPoints
|
|
248 |
mTmpSeg = mTmpTime.toInt()
|
|
249 |
mTmpVec = mTmpSeg
|
|
250 |
}
|
|
251 |
|
|
252 |
MODE_PATH ->
|
|
253 |
{
|
|
254 |
mTmpSeg = (2*time*(numPoints-1)).toInt()
|
|
255 |
|
|
256 |
if (time <= 0.5f) // this has to be <= (otherwise when effect ends at t=0.5, then time=1.0
|
|
257 |
{ // and end position is slightly not equal to the end point => might not get autodeleted!
|
|
258 |
mTmpTime = 2*time*(numPoints-1)
|
|
259 |
mTmpVec = mTmpSeg
|
|
260 |
}
|
|
261 |
else
|
|
262 |
{
|
|
263 |
mTmpTime = 2 * (1 - time) * (numPoints - 1)
|
|
264 |
mTmpVec = 2 * numPoints - 3 - mTmpSeg
|
|
265 |
}
|
|
266 |
}
|
|
267 |
|
|
268 |
MODE_JUMP ->
|
|
269 |
{
|
|
270 |
mTmpTime = time*(numPoints-1)
|
|
271 |
mTmpSeg = mTmpTime.toInt()
|
|
272 |
mTmpVec = mTmpSeg
|
|
273 |
}
|
|
274 |
|
|
275 |
else ->
|
|
276 |
{
|
|
277 |
mTmpVec = 0
|
|
278 |
mTmpSeg = 0
|
|
279 |
}
|
|
280 |
}
|
233 |
281 |
}
|
234 |
282 |
|
235 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
236 |
|
|
237 |
|
public static void onPause()
|
|
283 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
284 |
private fun valueAtPoint(t: Float, cache: VectorCache): Float
|
238 |
285 |
{
|
239 |
|
mPausedTime = System.currentTimeMillis();
|
240 |
|
}
|
|
286 |
var tmp: Float
|
|
287 |
var sum = 0.0f
|
241 |
288 |
|
242 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
289 |
for (d in 0..<dimension)
|
|
290 |
{
|
|
291 |
tmp = (3*cache.a[d]*t + 2*cache.b[d])*t + cache.c[d]
|
|
292 |
sum += tmp*tmp
|
|
293 |
}
|
243 |
294 |
|
244 |
|
protected void computeSegmentAndTime(float time)
|
245 |
|
{
|
246 |
|
switch(mMode)
|
247 |
|
{
|
248 |
|
case MODE_LOOP: mTmpTime= time*numPoints;
|
249 |
|
mTmpSeg = (int)mTmpTime;
|
250 |
|
mTmpVec = mTmpSeg;
|
251 |
|
break;
|
252 |
|
case MODE_PATH: mTmpSeg = (int)(2*time*(numPoints-1));
|
253 |
|
|
254 |
|
if( time<=0.5f ) // this has to be <= (otherwise when effect ends at t=0.5, then time=1.0
|
255 |
|
{ // and end position is slightly not equal to the end point => might not get autodeleted!
|
256 |
|
mTmpTime = 2*time*(numPoints-1);
|
257 |
|
mTmpVec = mTmpSeg;
|
258 |
|
}
|
259 |
|
else
|
260 |
|
{
|
261 |
|
mTmpTime = 2*(1-time)*(numPoints-1);
|
262 |
|
mTmpVec = 2*numPoints-3-mTmpSeg;
|
263 |
|
}
|
264 |
|
break;
|
265 |
|
case MODE_JUMP: mTmpTime= time*(numPoints-1);
|
266 |
|
mTmpSeg = (int)mTmpTime;
|
267 |
|
mTmpVec = mTmpSeg;
|
268 |
|
break;
|
269 |
|
default : mTmpVec = 0;
|
270 |
|
mTmpSeg = 0;
|
271 |
|
}
|
|
295 |
return sqrt(sum.toDouble()).toFloat()
|
272 |
296 |
}
|
273 |
297 |
|
274 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
275 |
|
|
276 |
|
private float valueAtPoint(float t, VectorCache cache)
|
|
298 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
299 |
protected fun smoothSpeed(time: Float, cache: VectorCache): Float
|
277 |
300 |
{
|
278 |
|
float tmp,sum = 0.0f;
|
279 |
|
|
280 |
|
for(int d=0; d<mDimension; d++)
|
281 |
|
{
|
282 |
|
tmp = (3*cache.a[d]*t + 2*cache.b[d])*t + cache.c[d];
|
283 |
|
sum += tmp*tmp;
|
284 |
|
}
|
|
301 |
val fndex = time*NUM_RATIO
|
|
302 |
val index = fndex.toInt()
|
|
303 |
val prev = if (index==0) 0.0f else cache.path_ratio[index-1]
|
|
304 |
val next = cache.path_ratio[index]
|
285 |
305 |
|
286 |
|
return (float)Math.sqrt(sum);
|
|
306 |
return prev + (next-prev) * (fndex-index)
|
287 |
307 |
}
|
288 |
308 |
|
289 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
290 |
|
|
291 |
|
protected float smoothSpeed(float time, VectorCache cache)
|
|
309 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
310 |
// First, compute the approx length of the segment from time=0 to time=(i+1)/NUM_TIME and store this
|
|
311 |
// in cache.path_ratio[i]. Then the last path_ratio is the length from 0 to 1, i.e. the total length
|
|
312 |
// of the segment.
|
|
313 |
// We do this by computing the integral from 0 to 1 of sqrt( (dx/dt)^2 + (dy/dt)^2 ) (i.e. the length
|
|
314 |
// of the segment) using the approx 'trapezoids' integration method.
|
|
315 |
//
|
|
316 |
// Then, for every i, divide path_ratio[i] by the total length to get the percentage of total path
|
|
317 |
// length covered at time i. At this time, path_ratio[3] = 0.45 means 'at time 3/NUM_RATIO, we cover
|
|
318 |
// 0.45 = 45% of the total length of the segment.
|
|
319 |
//
|
|
320 |
// Finally, invert this function (for quicker lookups in smoothSpeed) so that after this step,
|
|
321 |
// path_ratio[3] = 0.45 means 'at 45% of the time, we cover 3/NUM_RATIO distance'.
|
|
322 |
protected fun smoothOutSegment(cache: VectorCache)
|
292 |
323 |
{
|
293 |
|
float fndex = time*NUM_RATIO;
|
294 |
|
int index = (int)fndex;
|
295 |
|
float prev = index==0 ? 0.0f : cache.path_ratio[index-1];
|
296 |
|
float next = cache.path_ratio[index];
|
|
324 |
var vPrev: Float
|
|
325 |
var sum = 0.0f
|
|
326 |
var vNext = valueAtPoint(0.0f,cache)
|
297 |
327 |
|
298 |
|
return prev + (next-prev)*(fndex-index);
|
299 |
|
}
|
300 |
|
|
301 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
302 |
|
// First, compute the approx length of the segment from time=0 to time=(i+1)/NUM_TIME and store this
|
303 |
|
// in cache.path_ratio[i]. Then the last path_ratio is the length from 0 to 1, i.e. the total length
|
304 |
|
// of the segment.
|
305 |
|
// We do this by computing the integral from 0 to 1 of sqrt( (dx/dt)^2 + (dy/dt)^2 ) (i.e. the length
|
306 |
|
// of the segment) using the approx 'trapezoids' integration method.
|
307 |
|
//
|
308 |
|
// Then, for every i, divide path_ratio[i] by the total length to get the percentage of total path
|
309 |
|
// length covered at time i. At this time, path_ratio[3] = 0.45 means 'at time 3/NUM_RATIO, we cover
|
310 |
|
// 0.45 = 45% of the total length of the segment.
|
311 |
|
//
|
312 |
|
// Finally, invert this function (for quicker lookups in smoothSpeed) so that after this step,
|
313 |
|
// path_ratio[3] = 0.45 means 'at 45% of the time, we cover 3/NUM_RATIO distance'.
|
314 |
|
|
315 |
|
protected void smoothOutSegment(VectorCache cache)
|
316 |
|
{
|
317 |
|
float vPrev, sum = 0.0f;
|
318 |
|
float vNext = valueAtPoint(0.0f,cache);
|
319 |
|
|
320 |
|
for(int i=0; i<NUM_RATIO; i++)
|
321 |
|
{
|
322 |
|
vPrev = vNext;
|
323 |
|
vNext = valueAtPoint( (float)(i+1)/NUM_RATIO,cache);
|
324 |
|
sum += (vPrev+vNext);
|
325 |
|
cache.path_ratio[i] = sum;
|
326 |
|
}
|
327 |
|
|
328 |
|
float total = cache.path_ratio[NUM_RATIO-1];
|
|
328 |
for (i in 0..<NUM_RATIO)
|
|
329 |
{
|
|
330 |
vPrev = vNext
|
|
331 |
vNext = valueAtPoint( (i+1).toFloat()/NUM_RATIO, cache )
|
|
332 |
sum += (vPrev+vNext)
|
|
333 |
cache.path_ratio[i] = sum
|
|
334 |
}
|
329 |
335 |
|
330 |
|
for(int i=0; i<NUM_RATIO; i++) cache.path_ratio[i] /= total;
|
|
336 |
val total = cache.path_ratio[NUM_RATIO-1]
|
331 |
337 |
|
332 |
|
int writeIndex = 0;
|
333 |
|
float prev=0.0f, next, ratio= 1.0f/NUM_RATIO;
|
|
338 |
for (i in 0..<NUM_RATIO) cache.path_ratio[i] /= total
|
334 |
339 |
|
335 |
|
for(int readIndex=0; readIndex<NUM_RATIO; readIndex++)
|
336 |
|
{
|
337 |
|
next = cache.path_ratio[readIndex];
|
|
340 |
var writeIndex = 0
|
|
341 |
var prev = 0.0f
|
|
342 |
var next: Float
|
|
343 |
var ratio = 1.0f/NUM_RATIO
|
338 |
344 |
|
339 |
|
while( prev<ratio && ratio<=next )
|
|
345 |
for (readIndex in 0..<NUM_RATIO)
|
340 |
346 |
{
|
341 |
|
float a = (next-ratio)/(next-prev);
|
342 |
|
mTmpRatio[writeIndex] = (readIndex+1-a)/NUM_RATIO;
|
343 |
|
writeIndex++;
|
344 |
|
ratio = (writeIndex+1.0f)/NUM_RATIO;
|
345 |
|
}
|
|
347 |
next = cache.path_ratio[readIndex]
|
346 |
348 |
|
347 |
|
prev = next;
|
348 |
|
}
|
|
349 |
while (prev<ratio && ratio<=next)
|
|
350 |
{
|
|
351 |
val a = (next-ratio) / (next-prev)
|
|
352 |
mTmpRatio[writeIndex] = (readIndex+1-a) / NUM_RATIO
|
|
353 |
writeIndex++
|
|
354 |
ratio = (writeIndex+1.0f) / NUM_RATIO
|
|
355 |
}
|
349 |
356 |
|
350 |
|
System.arraycopy(mTmpRatio, 0, cache.path_ratio, 0, NUM_RATIO);
|
351 |
|
}
|
352 |
|
|
353 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
357 |
prev = next
|
|
358 |
}
|
354 |
359 |
|
355 |
|
protected float noise(float time,int vecNum)
|
356 |
|
{
|
357 |
|
float lower, upper, len;
|
358 |
|
float d = time*(NUM_NOISE+1);
|
359 |
|
int index = (int)d;
|
360 |
|
if( index>=NUM_NOISE+1 ) index=NUM_NOISE;
|
361 |
|
VectorNoise tmpN = vn.elementAt(vecNum);
|
362 |
|
|
363 |
|
float t = d-index;
|
364 |
|
t = t*t*(3-2*t);
|
365 |
|
|
366 |
|
switch(index)
|
367 |
|
{
|
368 |
|
case 0 : for(int i=0;i<mDimension-1;i++) mFactor[i] = mNoise[i+1]*tmpN.n[i+1][0]*t;
|
369 |
|
return time + mNoise[0]*(d*tmpN.n[0][0]-time);
|
370 |
|
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);
|
371 |
|
len = ((float)NUM_NOISE)/(NUM_NOISE+1);
|
372 |
|
lower = len + mNoise[0]*(tmpN.n[0][NUM_NOISE-1]-len);
|
373 |
|
return (1.0f-lower)*(d-NUM_NOISE) + lower;
|
374 |
|
default : float ya,yb;
|
375 |
|
|
376 |
|
for(int i=0;i<mDimension-1;i++)
|
377 |
|
{
|
378 |
|
yb = tmpN.n[i+1][index ];
|
379 |
|
ya = tmpN.n[i+1][index-1];
|
380 |
|
mFactor[i] = mNoise[i+1]*((yb-ya)*t+ya);
|
381 |
|
}
|
382 |
|
|
383 |
|
len = ((float)index)/(NUM_NOISE+1);
|
384 |
|
lower = len + mNoise[0]*(tmpN.n[0][index-1]-len);
|
385 |
|
len = ((float)index+1)/(NUM_NOISE+1);
|
386 |
|
upper = len + mNoise[0]*(tmpN.n[0][index ]-len);
|
387 |
|
|
388 |
|
return (upper-lower)*(d-index) + lower;
|
389 |
|
}
|
|
360 |
System.arraycopy(mTmpRatio, 0, cache.path_ratio, 0, NUM_RATIO)
|
390 |
361 |
}
|
391 |
362 |
|
392 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
393 |
|
// debugging only
|
394 |
|
|
395 |
|
private void printBase(String str)
|
|
363 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
364 |
protected fun noise(time: Float, vecNum: Int): Float
|
396 |
365 |
{
|
397 |
|
String s;
|
398 |
|
float t;
|
|
366 |
val lower: Float
|
|
367 |
val upper: Float
|
|
368 |
var len: Float
|
|
369 |
val d = time*(NUM_NOISE+1)
|
|
370 |
var index = d.toInt()
|
|
371 |
if (index >= NUM_NOISE+1) index = NUM_NOISE
|
|
372 |
val tmpN = vn!!.elementAt(vecNum)
|
399 |
373 |
|
400 |
|
for(int i=0; i<mDimension; i++)
|
401 |
|
{
|
402 |
|
s = "";
|
|
374 |
var t = d-index
|
|
375 |
t = t*t*(3-2*t)
|
403 |
376 |
|
404 |
|
for(int j=0; j<mDimension; j++)
|
|
377 |
when (index)
|
405 |
378 |
{
|
406 |
|
t = ((int)(1000*baseV[i][j]))/(1000.0f);
|
407 |
|
s+=(" "+t);
|
|
379 |
0 ->
|
|
380 |
{
|
|
381 |
var i = 0
|
|
382 |
while (i < dimension-1)
|
|
383 |
{
|
|
384 |
mFactor[i] = mNoise[i+1] * tmpN.n[i+1][0] * t
|
|
385 |
i++
|
|
386 |
}
|
|
387 |
return time + mNoise[0] * (d*tmpN.n[0][0]-time)
|
|
388 |
}
|
|
389 |
|
|
390 |
NUM_NOISE ->
|
|
391 |
{
|
|
392 |
var i = 0
|
|
393 |
while (i < dimension - 1)
|
|
394 |
{
|
|
395 |
mFactor[i] = mNoise[i+1] * tmpN.n[i+1][NUM_NOISE-1] * (1-t)
|
|
396 |
i++
|
|
397 |
}
|
|
398 |
|
|
399 |
len = (NUM_NOISE.toFloat()) / (NUM_NOISE+1)
|
|
400 |
lower = len + mNoise[0] * (tmpN.n[0][NUM_NOISE-1] - len)
|
|
401 |
return (1.0f-lower) * (d-NUM_NOISE) + lower
|
|
402 |
}
|
|
403 |
|
|
404 |
else ->
|
|
405 |
{
|
|
406 |
var ya: Float
|
|
407 |
var yb: Float
|
|
408 |
var i = 0
|
|
409 |
|
|
410 |
while (i < dimension-1)
|
|
411 |
{
|
|
412 |
yb = tmpN.n[i+1][index]
|
|
413 |
ya = tmpN.n[i+1][index-1]
|
|
414 |
mFactor[i] = mNoise[i+1] * ((yb-ya) * t+ya)
|
|
415 |
i++
|
|
416 |
}
|
|
417 |
|
|
418 |
len = (index.toFloat()) / (NUM_NOISE+1)
|
|
419 |
lower = len + mNoise[0] * (tmpN.n[0][index-1] - len)
|
|
420 |
len = (index.toFloat()+1) / (NUM_NOISE+1)
|
|
421 |
upper = len + mNoise[0] * (tmpN.n[0][index] - len)
|
|
422 |
|
|
423 |
return (upper-lower)*(d-index) + lower
|
|
424 |
}
|
408 |
425 |
}
|
409 |
|
DistortedLibrary.logMessage("Dynamic: "+str+" base "+i+" : " + s);
|
410 |
|
}
|
411 |
426 |
}
|
412 |
427 |
|
413 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
414 |
|
// debugging only
|
415 |
|
|
416 |
|
@SuppressWarnings("unused")
|
417 |
|
private void checkBase()
|
|
428 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
429 |
// debugging only
|
|
430 |
private fun printBase(str: String)
|
418 |
431 |
{
|
419 |
|
float tmp, cosA;
|
420 |
|
float[] len= new float[mDimension];
|
421 |
|
boolean error=false;
|
422 |
|
|
423 |
|
for(int i=0; i<mDimension; i++)
|
424 |
|
{
|
425 |
|
len[i] = 0.0f;
|
426 |
|
|
427 |
|
for(int k=0; k<mDimension; k++)
|
428 |
|
{
|
429 |
|
len[i] += baseV[i][k]*baseV[i][k];
|
430 |
|
}
|
|
432 |
var s: String
|
|
433 |
var t: Float
|
431 |
434 |
|
432 |
|
if( len[i] == 0.0f || len[0]/len[i] < 0.95f || len[0]/len[i]>1.05f )
|
|
435 |
for (i in 0..<dimension)
|
433 |
436 |
{
|
434 |
|
DistortedLibrary.logMessage("Dynamic: length of vector "+i+" : "+Math.sqrt(len[i]));
|
435 |
|
error = true;
|
|
437 |
s = ""
|
|
438 |
|
|
439 |
for (j in 0..<dimension)
|
|
440 |
{
|
|
441 |
t = ((1000*baseV[i][j]).toInt()) / (1000.0f)
|
|
442 |
s += (" $t")
|
|
443 |
}
|
|
444 |
DistortedLibrary.logMessage("Dynamic: $str base $i : $s")
|
436 |
445 |
}
|
437 |
|
}
|
438 |
|
|
439 |
|
for(int i=0; i<mDimension; i++)
|
440 |
|
for(int j=i+1; j<mDimension; j++)
|
441 |
|
{
|
442 |
|
tmp = 0.0f;
|
443 |
|
|
444 |
|
for(int k=0; k<mDimension; k++)
|
445 |
|
{
|
446 |
|
tmp += baseV[i][k]*baseV[j][k];
|
447 |
|
}
|
448 |
|
|
449 |
|
cosA = ( (len[i]==0.0f || len[j]==0.0f) ? 0.0f : tmp/(float)Math.sqrt(len[i]*len[j]));
|
450 |
|
|
451 |
|
if( cosA > 0.05f || cosA < -0.05f )
|
452 |
|
{
|
453 |
|
DistortedLibrary.logMessage("Dynamic: cos angle between vectors "+i+" and "+j+" : "+cosA);
|
454 |
|
error = true;
|
455 |
|
}
|
456 |
|
}
|
457 |
|
|
458 |
|
if( error ) printBase("");
|
459 |
446 |
}
|
460 |
447 |
|
461 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
462 |
|
|
463 |
|
int getNext(int curr, float time)
|
|
448 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
449 |
// debugging only
|
|
450 |
@Suppress("unused")
|
|
451 |
private fun checkBase()
|
464 |
452 |
{
|
465 |
|
switch(mMode)
|
466 |
|
{
|
467 |
|
case MODE_LOOP: return curr==numPoints-1 ? 0:curr+1;
|
468 |
|
case MODE_PATH: return time<0.5f ? (curr+1) : (curr==0 ? 1 : curr-1);
|
469 |
|
case MODE_JUMP: return curr==numPoints-1 ? 1:curr+1;
|
470 |
|
default : return 0;
|
471 |
|
}
|
472 |
|
}
|
473 |
|
|
474 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
453 |
var tmp: Float
|
|
454 |
var cosA: Float
|
|
455 |
val len = FloatArray(dimension)
|
|
456 |
var error = false
|
475 |
457 |
|
476 |
|
private void checkAngle(int index)
|
477 |
|
{
|
478 |
|
float cosA = 0.0f;
|
|
458 |
for (i in 0..<dimension)
|
|
459 |
{
|
|
460 |
len[i] = 0.0f
|
|
461 |
for (k in 0..<dimension) len[i] += baseV[i][k]*baseV[i][k]
|
|
462 |
|
|
463 |
if (len[i]==0.0f || len[0]/len[i] < 0.95f || len[0]/len[i] > 1.05f)
|
|
464 |
{
|
|
465 |
DistortedLibrary.logMessage("Dynamic: length of vector $i : " + sqrt(len[i].toDouble()) )
|
|
466 |
error = true
|
|
467 |
}
|
|
468 |
}
|
479 |
469 |
|
480 |
|
for(int k=0;k<mDimension; k++)
|
481 |
|
cosA += baseV[index][k]*old[k];
|
|
470 |
for (i in 0..<dimension)
|
|
471 |
for (j in i+1..<dimension)
|
|
472 |
{
|
|
473 |
tmp = 0.0f
|
|
474 |
for (k in 0..<dimension) tmp += baseV[i][k]*baseV[j][k]
|
482 |
475 |
|
483 |
|
if( cosA<0.0f )
|
484 |
|
{
|
485 |
|
for(int j=0; j<mDimension; j++)
|
486 |
|
baseV[index][j] = -baseV[index][j];
|
487 |
|
}
|
488 |
|
}
|
|
476 |
cosA = (if (len[i]==0.0f || len[j]==0.0f) 0.0f else tmp / sqrt((len[i]*len[j]).toDouble()).toFloat())
|
489 |
477 |
|
490 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
491 |
|
// helper function in case we are interpolating through exactly 2 points
|
|
478 |
if (cosA>0.05f || cosA<-0.05f)
|
|
479 |
{
|
|
480 |
DistortedLibrary.logMessage("Dynamic: cos angle between vectors $i and $j : $cosA")
|
|
481 |
error = true
|
|
482 |
}
|
|
483 |
}
|
492 |
484 |
|
493 |
|
protected void computeOrthonormalBase2(Static curr, Static next)
|
494 |
|
{
|
495 |
|
switch(mDimension)
|
496 |
|
{
|
497 |
|
case 1: Static1D curr1 = (Static1D)curr;
|
498 |
|
Static1D next1 = (Static1D)next;
|
499 |
|
baseV[0][0] = (next1.x-curr1.x);
|
500 |
|
break;
|
501 |
|
case 2: Static2D curr2 = (Static2D)curr;
|
502 |
|
Static2D next2 = (Static2D)next;
|
503 |
|
baseV[0][0] = (next2.x-curr2.x);
|
504 |
|
baseV[0][1] = (next2.y-curr2.y);
|
505 |
|
break;
|
506 |
|
case 3: Static3D curr3 = (Static3D)curr;
|
507 |
|
Static3D next3 = (Static3D)next;
|
508 |
|
baseV[0][0] = (next3.x-curr3.x);
|
509 |
|
baseV[0][1] = (next3.y-curr3.y);
|
510 |
|
baseV[0][2] = (next3.z-curr3.z);
|
511 |
|
break;
|
512 |
|
case 4: Static4D curr4 = (Static4D)curr;
|
513 |
|
Static4D next4 = (Static4D)next;
|
514 |
|
baseV[0][0] = (next4.x-curr4.x);
|
515 |
|
baseV[0][1] = (next4.y-curr4.y);
|
516 |
|
baseV[0][2] = (next4.z-curr4.z);
|
517 |
|
baseV[0][3] = (next4.w-curr4.w);
|
518 |
|
break;
|
519 |
|
case 5: Static5D curr5 = (Static5D)curr;
|
520 |
|
Static5D next5 = (Static5D)next;
|
521 |
|
baseV[0][0] = (next5.x-curr5.x);
|
522 |
|
baseV[0][1] = (next5.y-curr5.y);
|
523 |
|
baseV[0][2] = (next5.z-curr5.z);
|
524 |
|
baseV[0][3] = (next5.w-curr5.w);
|
525 |
|
baseV[0][4] = (next5.v-curr5.v);
|
526 |
|
break;
|
527 |
|
default: throw new RuntimeException("Unsupported dimension");
|
528 |
|
}
|
529 |
|
|
530 |
|
if( baseV[0][0] == 0.0f )
|
531 |
|
{
|
532 |
|
baseV[1][0] = 1.0f;
|
533 |
|
baseV[1][1] = 0.0f;
|
534 |
|
}
|
535 |
|
else
|
536 |
|
{
|
537 |
|
baseV[1][0] = 0.0f;
|
538 |
|
baseV[1][1] = 1.0f;
|
539 |
|
}
|
540 |
|
|
541 |
|
for(int i=2; i<mDimension; i++)
|
542 |
|
{
|
543 |
|
baseV[1][i] = 0.0f;
|
544 |
|
}
|
545 |
|
|
546 |
|
computeOrthonormalBase();
|
|
485 |
if (error) printBase("")
|
547 |
486 |
}
|
548 |
487 |
|
549 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
550 |
|
// helper function in case we are interpolating through more than 2 points
|
551 |
|
|
552 |
|
protected void computeOrthonormalBaseMore(float time,VectorCache vc)
|
|
488 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
489 |
fun getNext(curr: Int, time: Float): Int
|
553 |
490 |
{
|
554 |
|
for(int i=0; i<mDimension; i++)
|
555 |
|
{
|
556 |
|
baseV[0][i] = (3*vc.a[i]*time+2*vc.b[i])*time+vc.c[i]; // first derivative, i.e. velocity vector
|
557 |
|
old[i] = baseV[1][i];
|
558 |
|
baseV[1][i] = 6*vc.a[i]*time+2*vc.b[i]; // second derivative,i.e. acceleration vector
|
559 |
|
}
|
560 |
|
|
561 |
|
computeOrthonormalBase();
|
|
491 |
return when (mMode)
|
|
492 |
{
|
|
493 |
MODE_LOOP -> if (curr == numPoints-1) 0 else curr+1
|
|
494 |
MODE_PATH -> if (time < 0.5f ) (curr+1) else (if (curr==0) 1 else curr-1)
|
|
495 |
MODE_JUMP -> if (curr == numPoints-1) 1 else curr+1
|
|
496 |
else -> 0
|
|
497 |
}
|
562 |
498 |
}
|
563 |
499 |
|
564 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
565 |
|
// When this function gets called, baseV[0] and baseV[1] should have been filled with two mDimension-al
|
566 |
|
// vectors. This function then fills the rest of the baseV array with a mDimension-al Orthonormal base.
|
567 |
|
// (mDimension-2 vectors, pairwise orthogonal to each other and to the original 2). The function always
|
568 |
|
// leaves base[0] alone but generally speaking must adjust base[1] to make it orthogonal to base[0]!
|
569 |
|
// The whole baseV is then used to compute Noise.
|
570 |
|
//
|
571 |
|
// When computing noise of a point travelling along a N-dimensional path, there are three cases:
|
572 |
|
// a) we may be interpolating through 1 point, i.e. standing in place - nothing to do in this case
|
573 |
|
// b) we may be interpolating through 2 points, i.e. travelling along a straight line between them -
|
574 |
|
// then pass the velocity vector in baseV[0] and anything linearly independent in base[1].
|
575 |
|
// The output will then be discontinuous in dimensions>2 (sad corollary from the Hairy Ball Theorem)
|
576 |
|
// but we don't care - we are travelling along a straight line, so velocity (aka baseV[0]!) does
|
577 |
|
// not change.
|
578 |
|
// c) we may be interpolating through more than 2 points. Then interpolation formulas ensure the path
|
579 |
|
// will never be a straight line, even locally -> we can pass in baseV[0] and baseV[1] the velocity
|
580 |
|
// and the acceleration (first and second derivatives of the path) which are then guaranteed to be
|
581 |
|
// linearly independent. Then we can ensure this is continuous in dimensions <=4. This leaves
|
582 |
|
// dimension 5 (ATM WAVE is 5-dimensional) discontinuous -> WAVE will suffer from chaotic noise.
|
583 |
|
//
|
584 |
|
// Bear in mind here the 'normal' in 'orthonormal' means 'length equal to the length of the original
|
585 |
|
// velocity vector' (rather than the standard 1)
|
586 |
|
|
587 |
|
protected void computeOrthonormalBase()
|
|
500 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
501 |
private fun checkAngle(index: Int)
|
588 |
502 |
{
|
589 |
|
int last_non_zero=-1;
|
590 |
|
float tmp;
|
591 |
|
|
592 |
|
for(int i=0; i<mDimension; i++)
|
593 |
|
if( baseV[0][i] != 0.0f )
|
594 |
|
last_non_zero=i;
|
595 |
|
|
596 |
|
if( last_non_zero==-1 ) ///
|
597 |
|
{ // velocity is the 0 vector -> two
|
598 |
|
for(int i=0; i<mDimension-1; i++) // consecutive points we are interpolating
|
599 |
|
for(int j=0; j<mDimension; j++) // through are identical -> no noise,
|
600 |
|
baseV[i+1][j]= 0.0f; // set the base to 0 vectors.
|
601 |
|
} ///
|
602 |
|
else
|
603 |
|
{
|
604 |
|
for(int i=1; i<mDimension; i++) /// One iteration computes baseV[i][*]
|
605 |
|
{ // (aka b[i]), the i-th orthonormal vector.
|
606 |
|
buf[i-1]=0.0f; //
|
607 |
|
// We can use (modified!) Gram-Schmidt.
|
608 |
|
for(int k=0; k<mDimension; k++) //
|
609 |
|
{ //
|
610 |
|
if( i>=2 ) // b[0] = b[0]
|
611 |
|
{ // b[1] = b[1] - (<b[1],b[0]>/<b[0],b[0]>)*b[0]
|
612 |
|
old[k] = baseV[i][k]; // b[2] = b[2] - (<b[2],b[0]>/<b[0],b[0]>)*b[0] - (<b[2],b[1]>/<b[1],b[1]>)*b[1]
|
613 |
|
baseV[i][k]= (k==i-(last_non_zero>=i?1:0)) ? 1.0f : 0.0f; // 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]
|
614 |
|
} // (...)
|
615 |
|
// then b[i] = b[i] / |b[i]| ( Here really b[i] = b[i] / (|b[0]|/|b[i]|)
|
616 |
|
tmp = baseV[i-1][k]; //
|
617 |
|
buf[i-1] += tmp*tmp; //
|
618 |
|
} //
|
619 |
|
//
|
620 |
|
for(int j=0; j<i; j++) //
|
621 |
|
{ //
|
622 |
|
tmp = 0.0f; //
|
623 |
|
for(int k=0;k<mDimension; k++) tmp += baseV[i][k]*baseV[j][k]; //
|
624 |
|
tmp /= buf[j]; //
|
625 |
|
for(int k=0;k<mDimension; k++) baseV[i][k] -= tmp*baseV[j][k]; //
|
626 |
|
} //
|
627 |
|
//
|
628 |
|
checkAngle(i); //
|
629 |
|
} /// end compute baseV[i][*]
|
630 |
|
|
631 |
|
buf[mDimension-1]=0.0f; /// Normalize
|
632 |
|
for(int k=0; k<mDimension; k++) //
|
633 |
|
{ //
|
634 |
|
tmp = baseV[mDimension-1][k]; //
|
635 |
|
buf[mDimension-1] += tmp*tmp; //
|
636 |
|
} //
|
637 |
|
//
|
638 |
|
for(int i=1; i<mDimension; i++) //
|
639 |
|
{ //
|
640 |
|
tmp = (float)Math.sqrt(buf[0]/buf[i]); //
|
641 |
|
for(int k=0;k<mDimension; k++) baseV[i][k] *= tmp; //
|
642 |
|
} /// End Normalize
|
643 |
|
}
|
644 |
|
}
|
|
503 |
var cosA = 0.0f
|
|
504 |
for (k in 0..<dimension) cosA += baseV[index][k]*old[k]
|
645 |
505 |
|
646 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
647 |
|
|
648 |
|
abstract void interpolate(float[] buffer, int offset, float time);
|
649 |
|
|
650 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
651 |
|
// PUBLIC API
|
652 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
653 |
|
|
654 |
|
/**
|
655 |
|
* Sets the mode of the interpolation to Loop, Path or Jump.
|
656 |
|
* <ul>
|
657 |
|
* <li>Loop is when we go from the first point all the way to the last, and the back to the first through
|
658 |
|
* the shortest way.
|
659 |
|
* <li>Path is when we come back from the last point back to the first the same way we got there.
|
660 |
|
* <li>Jump is when we go from first to last and then jump straight back to the first.
|
661 |
|
* </ul>
|
662 |
|
*
|
663 |
|
* @param mode {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
|
664 |
|
*/
|
665 |
|
public void setMode(int mode)
|
666 |
|
{
|
667 |
|
mMode = mode;
|
|
506 |
if (cosA < 0.0f)
|
|
507 |
for (j in 0..<dimension) baseV[index][j] = -baseV[index][j]
|
668 |
508 |
}
|
669 |
509 |
|
670 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
671 |
|
/**
|
672 |
|
* Returns the number of Points this Dynamic has been fed with.
|
673 |
|
*
|
674 |
|
* @return the number of Points we are currently interpolating through.
|
675 |
|
*/
|
676 |
|
public synchronized int getNumPoints()
|
|
510 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
511 |
// helper function in case we are interpolating through exactly 2 points
|
|
512 |
protected fun computeOrthonormalBase2(curr: Static, next: Static)
|
677 |
513 |
{
|
678 |
|
return numPoints;
|
679 |
|
}
|
|
514 |
when (dimension)
|
|
515 |
{
|
|
516 |
1 ->
|
|
517 |
{
|
|
518 |
val curr1 = curr as Static1D
|
|
519 |
val next1 = next as Static1D
|
|
520 |
baseV[0][0] = (next1.x - curr1.x)
|
|
521 |
}
|
|
522 |
|
|
523 |
2 ->
|
|
524 |
{
|
|
525 |
val curr2 = curr as Static2D
|
|
526 |
val next2 = next as Static2D
|
|
527 |
baseV[0][0] = (next2.x - curr2.x)
|
|
528 |
baseV[0][1] = (next2.y - curr2.y)
|
|
529 |
}
|
|
530 |
|
|
531 |
3 ->
|
|
532 |
{
|
|
533 |
val curr3 = curr as Static3D
|
|
534 |
val next3 = next as Static3D
|
|
535 |
baseV[0][0] = (next3.x - curr3.x)
|
|
536 |
baseV[0][1] = (next3.y - curr3.y)
|
|
537 |
baseV[0][2] = (next3.z - curr3.z)
|
|
538 |
}
|
|
539 |
|
|
540 |
4 ->
|
|
541 |
{
|
|
542 |
val curr4 = curr as Static4D
|
|
543 |
val next4 = next as Static4D
|
|
544 |
baseV[0][0] = (next4.x - curr4.x)
|
|
545 |
baseV[0][1] = (next4.y - curr4.y)
|
|
546 |
baseV[0][2] = (next4.z - curr4.z)
|
|
547 |
baseV[0][3] = (next4.w - curr4.w)
|
|
548 |
}
|
|
549 |
|
|
550 |
5 ->
|
|
551 |
{
|
|
552 |
val curr5 = curr as Static5D
|
|
553 |
val next5 = next as Static5D
|
|
554 |
baseV[0][0] = (next5.x - curr5.x)
|
|
555 |
baseV[0][1] = (next5.y - curr5.y)
|
|
556 |
baseV[0][2] = (next5.z - curr5.z)
|
|
557 |
baseV[0][3] = (next5.w - curr5.w)
|
|
558 |
baseV[0][4] = (next5.v - curr5.v)
|
|
559 |
}
|
|
560 |
|
|
561 |
else -> throw RuntimeException("Unsupported dimension")
|
|
562 |
}
|
680 |
563 |
|
681 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
682 |
|
/**
|
683 |
|
* Sets how many revolutions we want to do.
|
684 |
|
* <p>
|
685 |
|
* Does not have to be an integer. What constitutes 'one revolution' depends on the MODE:
|
686 |
|
* {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
|
687 |
|
* Count<=0 means 'go on interpolating indefinitely'.
|
688 |
|
*
|
689 |
|
* @param count the number of times we want to interpolate between our collection of Points.
|
690 |
|
*/
|
691 |
|
public void setCount(float count)
|
692 |
|
{
|
693 |
|
mCount = count;
|
694 |
|
}
|
|
564 |
if (baseV[0][0] == 0.0f)
|
|
565 |
{
|
|
566 |
baseV[1][0] = 1.0f
|
|
567 |
baseV[1][1] = 0.0f
|
|
568 |
}
|
|
569 |
else
|
|
570 |
{
|
|
571 |
baseV[1][0] = 0.0f
|
|
572 |
baseV[1][1] = 1.0f
|
|
573 |
}
|
695 |
574 |
|
696 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
697 |
|
/**
|
698 |
|
* Return the number of revolutions this Dynamic will make.
|
699 |
|
* What constitutes 'one revolution' depends on the MODE:
|
700 |
|
* {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
|
701 |
|
*
|
702 |
|
* @return the number revolutions this Dynamic will make.
|
703 |
|
*/
|
704 |
|
public float getCount()
|
705 |
|
{
|
706 |
|
return mCount;
|
707 |
|
}
|
|
575 |
for (i in 2..<dimension) baseV[1][i] = 0.0f
|
708 |
576 |
|
709 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
710 |
|
/**
|
711 |
|
* Start running from the beginning again.
|
712 |
|
*
|
713 |
|
* If a Dynamic has been used already, and we want to use it again and start interpolating from the
|
714 |
|
* first Point, first we need to reset it using this method.
|
715 |
|
*/
|
716 |
|
public void resetToBeginning()
|
717 |
|
{
|
718 |
|
mStartTime = -1;
|
|
577 |
computeOrthonormalBase()
|
719 |
578 |
}
|
720 |
579 |
|
721 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
722 |
|
/**
|
723 |
|
* @param duration Number of milliseconds one revolution will take.
|
724 |
|
* What constitutes 'one revolution' depends on the MODE:
|
725 |
|
* {@link Dynamic#MODE_LOOP}, {@link Dynamic#MODE_PATH} or {@link Dynamic#MODE_JUMP}.
|
726 |
|
*/
|
727 |
|
public void setDuration(long duration)
|
|
580 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
581 |
// helper function in case we are interpolating through more than 2 points
|
|
582 |
protected fun computeOrthonormalBaseMore(time: Float, vc: VectorCache)
|
728 |
583 |
{
|
729 |
|
mDuration = duration;
|
730 |
|
}
|
|
584 |
for (i in 0..<dimension)
|
|
585 |
{
|
|
586 |
baseV[0][i] = (3*vc.a[i]*time + 2*vc.b[i])*time + vc.c[i] // first derivative, i.e. velocity vector
|
|
587 |
old[i] = baseV[1][i]
|
|
588 |
baseV[1][i] = 6*vc.a[i]*time + 2*vc.b[i] // second derivative,i.e. acceleration vector
|
|
589 |
}
|
731 |
590 |
|
732 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
733 |
|
/**
|
734 |
|
* @return Number of milliseconds one revolution will take.
|
735 |
|
*/
|
736 |
|
public long getDuration()
|
737 |
|
{
|
738 |
|
return mDuration;
|
|
591 |
computeOrthonormalBase()
|
|
592 |
}
|
|
593 |
|
|
594 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
595 |
// When this function gets called, baseV[0] and baseV[1] should have been filled with two mDimension-al
|
|
596 |
// vectors. This function then fills the rest of the baseV array with a mDimension-al Orthonormal base.
|
|
597 |
// (mDimension-2 vectors, pairwise orthogonal to each other and to the original 2). The function always
|
|
598 |
// leaves base[0] alone but generally speaking must adjust base[1] to make it orthogonal to base[0]!
|
|
599 |
// The whole baseV is then used to compute Noise.
|
|
600 |
//
|
|
601 |
// When computing noise of a point travelling along a N-dimensional path, there are three cases:
|
|
602 |
// a) we may be interpolating through 1 point, i.e. standing in place - nothing to do in this case
|
|
603 |
// b) we may be interpolating through 2 points, i.e. travelling along a straight line between them -
|
|
604 |
// then pass the velocity vector in baseV[0] and anything linearly independent in base[1].
|
|
605 |
// The output will then be discontinuous in dimensions>2 (sad corollary from the Hairy Ball Theorem)
|
|
606 |
// but we don't care - we are travelling along a straight line, so velocity (aka baseV[0]!) does
|
|
607 |
// not change.
|
|
608 |
// c) we may be interpolating through more than 2 points. Then interpolation formulas ensure the path
|
|
609 |
// will never be a straight line, even locally -> we can pass in baseV[0] and baseV[1] the velocity
|
|
610 |
// and the acceleration (first and second derivatives of the path) which are then guaranteed to be
|
|
611 |
// linearly independent. Then we can ensure this is continuous in dimensions <=4. This leaves
|
|
612 |
// dimension 5 (ATM WAVE is 5-dimensional) discontinuous -> WAVE will suffer from chaotic noise.
|
|
613 |
//
|
|
614 |
// Bear in mind here the 'normal' in 'orthonormal' means 'length equal to the length of the original
|
|
615 |
// velocity vector' (rather than the standard 1)
|
|
616 |
protected fun computeOrthonormalBase()
|
|
617 |
{
|
|
618 |
var lastnonzero = -1
|
|
619 |
var tmp: Float
|
|
620 |
|
|
621 |
for (i in 0..<dimension)
|
|
622 |
if (baseV[0][i] != 0.0f) lastnonzero = i
|
|
623 |
|
|
624 |
if (lastnonzero == -1)
|
|
625 |
{ // velocity is the 0 vector -> two
|
|
626 |
for (i in 0..<dimension-1) // consecutive points we are interpolating
|
|
627 |
for (j in 0..<dimension) // through are identical -> no noise,
|
|
628 |
baseV[i+1][j] = 0.0f // set the base to 0 vectors.
|
|
629 |
}
|
|
630 |
else
|
|
631 |
{
|
|
632 |
for (i in 1..<dimension) // One iteration computes baseV[i][*]
|
|
633 |
{ // (aka b[i]), the i-th orthonormal vector.
|
|
634 |
buf[i-1] = 0.0f //
|
|
635 |
// We can use (modified!) Gram-Schmidt.
|
|
636 |
for (k in 0..<dimension) //
|
|
637 |
{ //
|
|
638 |
if (i>=2) // b[0] = b[0]
|
|
639 |
{ // b[1] = b[1] - (<b[1],b[0]>/<b[0],b[0]>)*b[0]
|
|
640 |
old[k] = baseV[i][k] // b[2] = b[2] - (<b[2],b[0]>/<b[0],b[0]>)*b[0] - (<b[2],b[1]>/<b[1],b[1]>)*b[1]
|
|
641 |
baseV[i][k] = if (k== i - (if (lastnonzero >= i) 1 else 0)) 1.0f else 0.0f // 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]
|
|
642 |
} // (...)
|
|
643 |
// then b[i] = b[i] / |b[i]| ( Here really b[i] = b[i] / (|b[0]|/|b[i]|)
|
|
644 |
tmp = baseV[i-1][k] //
|
|
645 |
buf[i-1] += tmp*tmp //
|
|
646 |
} //
|
|
647 |
//
|
|
648 |
for (j in 0..<i) //
|
|
649 |
{ //
|
|
650 |
tmp = 0.0f //
|
|
651 |
for (k in 0..<dimension) tmp += baseV[i][k]*baseV[j][k] //
|
|
652 |
tmp /= buf[j] //
|
|
653 |
for (k in 0..<dimension) baseV[i][k] -= tmp*baseV[j][k] //
|
|
654 |
} //
|
|
655 |
//
|
|
656 |
checkAngle(i) //
|
|
657 |
} //
|
|
658 |
// end compute baseV[i][*]
|
|
659 |
buf[dimension-1] = 0.0f
|
|
660 |
// Normalize
|
|
661 |
for (k in 0..<dimension) //
|
|
662 |
{ //
|
|
663 |
tmp = baseV[dimension-1][k] //
|
|
664 |
buf[dimension-1] += tmp*tmp //
|
|
665 |
} //
|
|
666 |
//
|
|
667 |
for (i in 1..<dimension) //
|
|
668 |
{ //
|
|
669 |
tmp = sqrt((buf[0] / buf[i]).toDouble()).toFloat() //
|
|
670 |
for (k in 0..<dimension) baseV[i][k] *= tmp //
|
|
671 |
} // End Normalize
|
|
672 |
}
|
739 |
673 |
}
|
740 |
674 |
|
741 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
742 |
|
/**
|
743 |
|
* @param convexity If set to the default (1.0f) then interpolation between 4 points
|
744 |
|
* (1,0) (0,1) (-1,0) (0,-1) will be the natural circle centered at (0,0) with radius 1.
|
745 |
|
* The less it is, the less convex the circle becomes, ultimately when convexity=0.0f
|
746 |
|
* then the interpolation shape will be straight lines connecting the four points.
|
747 |
|
* Further setting this to negative values will make the shape concave.
|
748 |
|
* Valid values: all floats. (although probably only something around (0,2) actually
|
749 |
|
* makes sense)
|
750 |
|
*/
|
751 |
|
public void setConvexity(float convexity)
|
752 |
|
{
|
753 |
|
if( mConvexity!=convexity )
|
754 |
|
{
|
755 |
|
mConvexity = convexity;
|
756 |
|
cacheDirty = true;
|
757 |
|
}
|
|
675 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
676 |
abstract fun interpolate(buffer: FloatArray, offset: Int, tm: Float)
|
|
677 |
|
|
678 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
679 |
// PUBLIC API
|
|
680 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
681 |
/** Sets the mode of the interpolation to Loop, Path or Jump.
|
|
682 |
*
|
|
683 |
* Loop is when we go from the first point all the way to the last, and the back to the first through the shortest way.
|
|
684 |
* Path is when we come back from the last point back to the first the same way we got there.
|
|
685 |
* Jump is when we go from first to last and then jump straight back to the first.
|
|
686 |
*
|
|
687 |
* @param mode [Dynamic.MODE_LOOP], [Dynamic.MODE_PATH] or [Dynamic.MODE_JUMP].
|
|
688 |
*/
|
|
689 |
fun setMode(mode: Int) { mMode = mode }
|
|
690 |
|
|
691 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
692 |
/** Start running from the beginning again.
|
|
693 |
*
|
|
694 |
* If a Dynamic has been used already, and we want to use it again and start interpolating from the
|
|
695 |
* first Point, first we need to reset it using this method.
|
|
696 |
*/
|
|
697 |
fun resetToBeginning() { mStartTime = -1 }
|
|
698 |
|
|
699 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
700 |
/** Sets the access type this Dynamic will be working in.
|
|
701 |
*
|
|
702 |
* @param type [Dynamic.ACCESS_TYPE_RANDOM] or [Dynamic.ACCESS_TYPE_SEQUENTIAL].
|
|
703 |
*/
|
|
704 |
fun setAccessType(type: Int)
|
|
705 |
{
|
|
706 |
mAccessType = type
|
|
707 |
mLastPos = -1.0
|
|
708 |
}
|
|
709 |
|
|
710 |
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
711 |
/** Sets the way we compute the interpolation speed.
|
|
712 |
*
|
|
713 |
* @param mode [Dynamic.SPEED_MODE_SMOOTH] or [Dynamic.SPEED_MODE_SEGMENT_CONSTANT] or
|
|
714 |
* [Dynamic.SPEED_MODE_GLOBALLY_CONSTANT]
|
|
715 |
*/
|
|
716 |
fun setSpeedMode(mode: Int)
|
|
717 |
{
|
|
718 |
if (mSpeedMode!=mode)
|
|
719 |
{
|
|
720 |
if (mSpeedMode==SPEED_MODE_SMOOTH)
|
|
721 |
for (i in 0..<numPoints) smoothOutSegment(vc!!.elementAt(i))
|
|
722 |
mSpeedMode = mode
|
|
723 |
}
|
758 |
724 |
}
|
759 |
725 |
|
760 |
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
761 |
|
/**
|
762 |
|
* @return See {@link Dynamic#setConvexity(float)}
|
763 |
|
*/
|
764 |
|
public float getConvexity()
|
transition the 'type' package