1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.constraintlayout.core.motion.utils; 17 18 import androidx.constraintlayout.core.motion.MotionWidget; 19 20 import java.text.DecimalFormat; 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.Comparator; 24 25 /** 26 * Provide the engine for executing cycles. 27 * KeyCycleOscillator 28 * 29 * 30 */ 31 public abstract class KeyCycleOscillator { 32 private static final String TAG = "KeyCycleOscillator"; 33 private CurveFit mCurveFit; 34 private CycleOscillator mCycleOscillator; 35 private String mType; 36 private int mWaveShape = 0; 37 private String mWaveString = null; 38 39 public int mVariesBy = 0; // 0 = position, 2=path 40 ArrayList<WavePoint> mWavePoints = new ArrayList<>(); 41 42 // @TODO: add description makeWidgetCycle(String attribute)43 public static KeyCycleOscillator makeWidgetCycle(String attribute) { 44 if (attribute.equals(TypedValues.AttributesType.S_PATH_ROTATE)) { 45 return new PathRotateSet(attribute); 46 } 47 return new CoreSpline(attribute); 48 } 49 50 private static class CoreSpline extends KeyCycleOscillator { 51 String mType; 52 int mTypeId; 53 CoreSpline(String str)54 CoreSpline(String str) { 55 mType = str; 56 mTypeId = TypedValues.CycleType.getId(mType); 57 } 58 59 @Override setProperty(MotionWidget widget, float t)60 public void setProperty(MotionWidget widget, float t) { 61 widget.setValue(mTypeId, get(t)); 62 } 63 } 64 65 public static class PathRotateSet extends KeyCycleOscillator { 66 String mType; 67 int mTypeId; 68 PathRotateSet(String str)69 public PathRotateSet(String str) { 70 mType = str; 71 mTypeId = TypedValues.CycleType.getId(mType); 72 } 73 74 @Override setProperty(MotionWidget widget, float t)75 public void setProperty(MotionWidget widget, float t) { 76 widget.setValue(mTypeId, get(t)); 77 } 78 79 // @TODO: add description setPathRotate(MotionWidget view, float t, double dx, double dy)80 public void setPathRotate(MotionWidget view, float t, double dx, double dy) { 81 view.setRotationZ(get(t) + (float) Math.toDegrees(Math.atan2(dy, dx))); 82 } 83 } 84 85 // @TODO: add description variesByPath()86 public boolean variesByPath() { 87 return mVariesBy == 1; 88 } 89 90 static class WavePoint { 91 int mPosition; 92 float mValue; 93 float mOffset; 94 float mPeriod; 95 float mPhase; 96 WavePoint(int position, float period, float offset, float phase, float value)97 WavePoint(int position, float period, float offset, float phase, float value) { 98 mPosition = position; 99 mValue = value; 100 mOffset = offset; 101 mPeriod = period; 102 mPhase = phase; 103 } 104 } 105 106 @Override toString()107 public String toString() { 108 String str = mType; 109 DecimalFormat df = new DecimalFormat("##.##"); 110 for (WavePoint wp : mWavePoints) { 111 str += "[" + wp.mPosition + " , " + df.format(wp.mValue) + "] "; 112 } 113 return str; 114 } 115 setType(String type)116 public void setType(String type) { 117 mType = type; 118 } 119 120 // @TODO: add description get(float t)121 public float get(float t) { 122 return (float) mCycleOscillator.getValues(t); 123 } 124 125 // @TODO: add description getSlope(float position)126 public float getSlope(float position) { 127 return (float) mCycleOscillator.getSlope(position); 128 } 129 getCurveFit()130 public CurveFit getCurveFit() { 131 return mCurveFit; 132 } 133 setCustom(Object custom)134 protected void setCustom(Object custom) { 135 136 } 137 138 /** 139 * sets a oscillator wave point 140 * 141 * @param framePosition the position 142 * @param shape the wave shape 143 * @param waveString the wave string 144 * @param variesBy only varies by path supported for now 145 * @param period the period of the wave 146 * @param offset the offset value 147 * @param phase the phase of the new wavepoint 148 * @param value the adder 149 * @param custom The ConstraintAttribute used to set the value 150 */ setPoint(int framePosition, int shape, String waveString, int variesBy, float period, float offset, float phase, float value, Object custom)151 public void setPoint(int framePosition, 152 int shape, 153 String waveString, 154 int variesBy, 155 float period, 156 float offset, 157 float phase, 158 float value, 159 Object custom) { 160 mWavePoints.add(new WavePoint(framePosition, period, offset, phase, value)); 161 if (variesBy != -1) { 162 mVariesBy = variesBy; 163 } 164 mWaveShape = shape; 165 setCustom(custom); 166 mWaveString = waveString; 167 } 168 169 /** 170 * sets a oscillator wave point 171 * 172 * @param framePosition the position 173 * @param shape the wave shape 174 * @param waveString the wave string 175 * @param variesBy only varies by path supported for now 176 * @param period the period of the wave 177 * @param offset the offset value 178 * @param phase the phase of the new wavepoint 179 * @param value the adder 180 */ setPoint(int framePosition, int shape, String waveString, int variesBy, float period, float offset, float phase, float value)181 public void setPoint(int framePosition, 182 int shape, 183 String waveString, 184 int variesBy, 185 float period, 186 float offset, 187 float phase, 188 float value) { 189 mWavePoints.add(new WavePoint(framePosition, period, offset, phase, value)); 190 if (variesBy != -1) { 191 mVariesBy = variesBy; 192 } 193 mWaveShape = shape; 194 mWaveString = waveString; 195 } 196 197 // @TODO: add description setup(float pathLength)198 public void setup(float pathLength) { 199 int count = mWavePoints.size(); 200 if (count == 0) { 201 return; 202 } 203 Collections.sort(mWavePoints, new Comparator<WavePoint>() { 204 @Override 205 public int compare(WavePoint lhs, WavePoint rhs) { 206 return Integer.compare(lhs.mPosition, rhs.mPosition); 207 } 208 }); 209 double[] time = new double[count]; 210 double[][] values = new double[count][3]; 211 mCycleOscillator = new CycleOscillator(mWaveShape, mWaveString, mVariesBy, count); 212 int i = 0; 213 for (WavePoint wp : mWavePoints) { 214 time[i] = wp.mPeriod * 1E-2; 215 values[i][0] = wp.mValue; 216 values[i][1] = wp.mOffset; 217 values[i][2] = wp.mPhase; 218 mCycleOscillator.setPoint(i, wp.mPosition, wp.mPeriod, 219 wp.mOffset, wp.mPhase, wp.mValue); 220 i++; 221 } 222 mCycleOscillator.setup(pathLength); 223 mCurveFit = CurveFit.get(CurveFit.SPLINE, time, values); 224 } 225 226 static class CycleOscillator { 227 static final int UNSET = -1; // -1 is typically used through out android to the UNSET value 228 private static final String TAG = "CycleOscillator"; 229 @SuppressWarnings("unused") private final int mVariesBy; 230 Oscillator mOscillator = new Oscillator(); 231 private final int mOffst = 0; 232 private final int mPhase = 1; 233 private final int mValue = 2; 234 235 float[] mValues; 236 double[] mPosition; 237 float[] mPeriod; 238 float[] mOffsetArr; // offsets will be spline interpolated 239 float[] mPhaseArr; // phase will be spline interpolated 240 float[] mScale; // scales will be spline interpolated 241 int mWaveShape; 242 CurveFit mCurveFit; 243 double[] mSplineValueCache; // for the return value of the curve fit 244 double[] mSplineSlopeCache; // for the return value of the curve fit 245 float mPathLength; 246 CycleOscillator(int waveShape, String customShape, int variesBy, int steps)247 CycleOscillator(int waveShape, String customShape, int variesBy, int steps) { 248 mWaveShape = waveShape; 249 mVariesBy = variesBy; 250 mOscillator.setType(waveShape, customShape); 251 mValues = new float[steps]; 252 mPosition = new double[steps]; 253 mPeriod = new float[steps]; 254 mOffsetArr = new float[steps]; 255 mPhaseArr = new float[steps]; 256 mScale = new float[steps]; 257 } 258 getValues(float time)259 public double getValues(float time) { 260 if (mCurveFit != null) { 261 mCurveFit.getPos(time, mSplineValueCache); 262 } else { // only one value no need to interpolate 263 mSplineValueCache[mOffst] = mOffsetArr[0]; 264 mSplineValueCache[mPhase] = mPhaseArr[0]; 265 mSplineValueCache[mValue] = mValues[0]; 266 267 } 268 double offset = mSplineValueCache[mOffst]; 269 double phase = mSplineValueCache[this.mPhase]; 270 double waveValue = mOscillator.getValue(time, phase); 271 return offset + waveValue * mSplineValueCache[mValue]; 272 } 273 getLastPhase()274 public double getLastPhase() { 275 return mSplineValueCache[1]; 276 } 277 getSlope(float time)278 public double getSlope(float time) { 279 if (mCurveFit != null) { 280 mCurveFit.getSlope(time, mSplineSlopeCache); 281 mCurveFit.getPos(time, mSplineValueCache); 282 } else { // only one value no need to interpolate 283 mSplineSlopeCache[mOffst] = 0; 284 mSplineSlopeCache[mPhase] = 0; 285 mSplineSlopeCache[mValue] = 0; 286 } 287 double waveValue = mOscillator.getValue(time, mSplineValueCache[mPhase]); 288 double waveSlope = mOscillator.getSlope(time, 289 mSplineValueCache[mPhase], mSplineSlopeCache[mPhase]); 290 return mSplineSlopeCache[mOffst] + waveValue * mSplineSlopeCache[mValue] 291 + waveSlope * mSplineValueCache[mValue]; 292 } 293 294 /** 295 * 296 */ setPoint(int index, int framePosition, float wavePeriod, float offset, float phase, float values)297 public void setPoint(int index, 298 int framePosition, 299 float wavePeriod, 300 float offset, 301 float phase, 302 float values) { 303 mPosition[index] = framePosition / 100.0; 304 mPeriod[index] = wavePeriod; 305 mOffsetArr[index] = offset; 306 mPhaseArr[index] = phase; 307 mValues[index] = values; 308 } 309 setup(float pathLength)310 public void setup(float pathLength) { 311 mPathLength = pathLength; 312 double[][] splineValues = new double[mPosition.length][3]; 313 mSplineValueCache = new double[2 + mValues.length]; 314 mSplineSlopeCache = new double[2 + mValues.length]; 315 if (mPosition[0] > 0) { 316 mOscillator.addPoint(0, mPeriod[0]); 317 } 318 int last = mPosition.length - 1; 319 if (mPosition[last] < 1.0f) { 320 mOscillator.addPoint(1, mPeriod[last]); 321 } 322 323 for (int i = 0; i < splineValues.length; i++) { 324 splineValues[i][mOffst] = mOffsetArr[i]; 325 splineValues[i][mPhase] = mPhaseArr[i]; 326 splineValues[i][mValue] = mValues[i]; 327 mOscillator.addPoint(mPosition[i], mPeriod[i]); 328 } 329 330 // TODO: add mVariesBy and get total time and path length 331 mOscillator.normalize(); 332 if (mPosition.length > 1) { 333 mCurveFit = CurveFit.get(CurveFit.SPLINE, mPosition, splineValues); 334 } else { 335 mCurveFit = null; 336 } 337 } 338 } 339 340 // @TODO: add description setProperty(MotionWidget widget, float t)341 public void setProperty(MotionWidget widget, float t) { 342 343 } 344 345 } 346