1 /*
2  * Copyright (C) 2021 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;
17 
18 import static androidx.constraintlayout.core.motion.MotionWidget.UNSET;
19 
20 import androidx.constraintlayout.core.motion.key.MotionKeyPosition;
21 import androidx.constraintlayout.core.motion.utils.Easing;
22 import androidx.constraintlayout.core.motion.utils.Utils;
23 
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.Set;
27 
28 /**
29  * This is used to capture and play back path of the layout.
30  * It is used to set the bounds of the view (view.layout(l, t, r, b))
31  */
32 public class MotionPaths implements Comparable<MotionPaths> {
33     public static final String TAG = "MotionPaths";
34     public static final boolean DEBUG = false;
35     public static final boolean OLD_WAY = false; // the computes the positions the old way
36     static final int OFF_POSITION = 0;
37     static final int OFF_X = 1;
38     static final int OFF_Y = 2;
39     static final int OFF_WIDTH = 3;
40     static final int OFF_HEIGHT = 4;
41     static final int OFF_PATH_ROTATE = 5;
42 
43     // mode and type have same numbering scheme
44     public static final int PERPENDICULAR = MotionKeyPosition.TYPE_PATH;
45     public static final int CARTESIAN = MotionKeyPosition.TYPE_CARTESIAN;
46     public static final int SCREEN = MotionKeyPosition.TYPE_SCREEN;
47     static String[] sNames = {"position", "x", "y", "width", "height", "pathRotate"};
48     public String mId;
49     Easing mKeyFrameEasing;
50     int mDrawPath = 0;
51     float mTime;
52     float mPosition;
53     float mX;
54     float mY;
55     float mWidth;
56     float mHeight;
57     float mPathRotate = Float.NaN;
58     float mProgress = Float.NaN;
59     int mPathMotionArc = UNSET;
60     String mAnimateRelativeTo = null;
61     float mRelativeAngle = Float.NaN;
62     Motion mRelativeToController = null;
63 
64     HashMap<String, CustomVariable> mCustomAttributes = new HashMap<>();
65     int mMode = 0; // how was this point computed 1=perpendicular 2=deltaRelative
66     int mAnimateCircleAngleTo; // since angles loop there are 4 ways we can pic direction
67 
MotionPaths()68     public MotionPaths() {
69     }
70 
71     /**
72      * set up with Cartesian
73      */
initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)74     void initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
75         float position = c.mFramePosition / 100f;
76         MotionPaths point = this;
77         point.mTime = position;
78 
79         mDrawPath = c.mDrawPath;
80         float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
81         float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
82         float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
83         float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
84 
85         point.mPosition = point.mTime;
86 
87         float path = position; // the position on the path
88 
89         float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
90         float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
91         float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
92         float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
93         float pathVectorX = endCenterX - startCenterX;
94         float pathVectorY = endCenterY - startCenterY;
95         point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
96         point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
97         point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
98         point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
99 
100         float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX;
101         float dydx = Float.isNaN(c.mAltPercentY) ? 0 : c.mAltPercentY;
102         float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY;
103         float dxdy = Float.isNaN(c.mAltPercentX) ? 0 : c.mAltPercentX;
104         point.mMode = MotionPaths.CARTESIAN;
105         point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx
106                 + pathVectorY * dxdy - scaleX * scaleWidth / 2);
107         point.mY = (int) (startTimePoint.mY + pathVectorX * dydx
108                 + pathVectorY * dydy - scaleY * scaleHeight / 2);
109 
110         point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
111         point.mPathMotionArc = c.mPathMotionArc;
112     }
113 
114     /**
115      * takes the new keyPosition
116      */
MotionPaths(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)117     public MotionPaths(int parentWidth,
118             int parentHeight,
119             MotionKeyPosition c,
120             MotionPaths startTimePoint,
121             MotionPaths endTimePoint) {
122         if (startTimePoint.mAnimateRelativeTo != null) {
123             initPolar(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
124             return;
125         }
126         switch (c.mPositionType) {
127             case MotionKeyPosition.TYPE_SCREEN:
128                 initScreen(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
129                 return;
130             case MotionKeyPosition.TYPE_PATH:
131                 initPath(c, startTimePoint, endTimePoint);
132                 return;
133             default:
134             case MotionKeyPosition.TYPE_CARTESIAN:
135                 initCartesian(c, startTimePoint, endTimePoint);
136                 return;
137         }
138     }
139 
initPolar(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths s, MotionPaths e)140     void initPolar(int parentWidth,
141             int parentHeight,
142             MotionKeyPosition c,
143             MotionPaths s,
144             MotionPaths e) {
145         float position = c.mFramePosition / 100f;
146         this.mTime = position;
147         mDrawPath = c.mDrawPath;
148         this.mMode = c.mPositionType; // mode and type have same numbering scheme
149         float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
150         float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
151         float scaleX = e.mWidth - s.mWidth;
152         float scaleY = e.mHeight - s.mHeight;
153         this.mPosition = this.mTime;
154         mWidth = (int) (s.mWidth + scaleX * scaleWidth);
155         mHeight = (int) (s.mHeight + scaleY * scaleHeight);
156         @SuppressWarnings("unused") float startfactor = 1 - position;
157         @SuppressWarnings("unused") float endfactor = position;
158 
159         switch (c.mPositionType) {
160             case MotionKeyPosition.TYPE_SCREEN:
161                 this.mX = Float.isNaN(c.mPercentX) ? (position * (e.mX - s.mX) + s.mX)
162                         : c.mPercentX * Math.min(scaleHeight, scaleWidth);
163                 this.mY = Float.isNaN(c.mPercentY)
164                         ? (position * (e.mY - s.mY) + s.mY) : c.mPercentY;
165                 break;
166 
167             case MotionKeyPosition.TYPE_PATH:
168                 this.mX = (Float.isNaN(c.mPercentX)
169                         ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
170                 this.mY = (Float.isNaN(c.mPercentY)
171                         ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
172                 break;
173             default:
174             case MotionKeyPosition.TYPE_CARTESIAN:
175                 this.mX = (Float.isNaN(c.mPercentX)
176                         ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
177                 this.mY = (Float.isNaN(c.mPercentY)
178                         ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
179                 break;
180         }
181 
182         this.mAnimateRelativeTo = s.mAnimateRelativeTo;
183         this.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
184         this.mPathMotionArc = c.mPathMotionArc;
185     }
186 
187     // @TODO: add description
setupRelative(Motion mc, MotionPaths relative)188     public void setupRelative(Motion mc, MotionPaths relative) {
189         double dx = mX + mWidth / 2 - relative.mX - relative.mWidth / 2;
190         double dy = mY + mHeight / 2 - relative.mY - relative.mHeight / 2;
191         mRelativeToController = mc;
192 
193         mX = (float) Math.hypot(dy, dx);
194         if (Float.isNaN(mRelativeAngle)) {
195             mY = (float) (Math.atan2(dy, dx) + Math.PI / 2);
196         } else {
197             mY = (float) Math.toRadians(mRelativeAngle);
198         }
199     }
200 
initScreen(int parentWidth, int parentHeight, MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)201     void initScreen(int parentWidth,
202             int parentHeight,
203             MotionKeyPosition c,
204             MotionPaths startTimePoint,
205             MotionPaths endTimePoint) {
206         float position = c.mFramePosition / 100f;
207         MotionPaths point = this;
208         point.mTime = position;
209 
210         mDrawPath = c.mDrawPath;
211         float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
212         float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
213 
214         float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
215         float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
216 
217         point.mPosition = point.mTime;
218 
219         float path = position; // the position on the path
220 
221         float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
222         float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
223         float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
224         float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
225         float pathVectorX = endCenterX - startCenterX;
226         float pathVectorY = endCenterY - startCenterY;
227         point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
228         point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
229         point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
230         point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
231 
232         point.mMode = MotionPaths.SCREEN;
233         if (!Float.isNaN(c.mPercentX)) {
234             parentWidth -= (int) point.mWidth;
235             point.mX = (int) (c.mPercentX * parentWidth);
236         }
237         if (!Float.isNaN(c.mPercentY)) {
238             parentHeight -= (int) point.mHeight;
239             point.mY = (int) (c.mPercentY * parentHeight);
240         }
241 
242         point.mAnimateRelativeTo = mAnimateRelativeTo;
243         point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
244         point.mPathMotionArc = c.mPathMotionArc;
245     }
246 
initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint)247     void initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
248 
249         float position = c.mFramePosition / 100f;
250         MotionPaths point = this;
251         point.mTime = position;
252 
253         mDrawPath = c.mDrawPath;
254         float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
255         float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
256 
257         float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
258         float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
259 
260         point.mPosition = point.mTime;
261 
262         float path = Float.isNaN(c.mPercentX) ? position : c.mPercentX; // the position on the path
263 
264         float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
265         float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
266         float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
267         float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
268         float pathVectorX = endCenterX - startCenterX;
269         float pathVectorY = endCenterY - startCenterY;
270         point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
271         point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
272         point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
273         point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
274         float perpendicular = Float.isNaN(c.mPercentY)
275                 ? 0 : c.mPercentY; // the position on the path
276         float perpendicularX = -pathVectorY;
277         float perpendicularY = pathVectorX;
278 
279         float normalX = perpendicularX * perpendicular;
280         float normalY = perpendicularY * perpendicular;
281         point.mMode = MotionPaths.PERPENDICULAR;
282         point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
283         point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
284         point.mX += normalX;
285         point.mY += normalY;
286 
287         point.mAnimateRelativeTo = mAnimateRelativeTo;
288         point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
289         point.mPathMotionArc = c.mPathMotionArc;
290     }
291 
xRotate(float sin, float cos, float cx, float cy, float x, float y)292     private static float xRotate(float sin, float cos, float cx, float cy, float x, float y) {
293         x = x - cx;
294         y = y - cy;
295         return x * cos - y * sin + cx;
296     }
297 
yRotate(float sin, float cos, float cx, float cy, float x, float y)298     private static float yRotate(float sin, float cos, float cx, float cy, float x, float y) {
299         x = x - cx;
300         y = y - cy;
301         return x * sin + y * cos + cy;
302     }
303 
diff(float a, float b)304     private boolean diff(float a, float b) {
305         if (Float.isNaN(a) || Float.isNaN(b)) {
306             return Float.isNaN(a) != Float.isNaN(b);
307         }
308         return Math.abs(a - b) > 0.000001f;
309     }
310 
different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode)311     void different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode) {
312         int c = 0;
313         boolean diffx = diff(mX, points.mX);
314         boolean diffy = diff(mY, points.mY);
315         mask[c++] |= diff(mPosition, points.mPosition);
316         mask[c++] |= diffx || diffy || arcMode;
317         mask[c++] |= diffx || diffy || arcMode;
318         mask[c++] |= diff(mWidth, points.mWidth);
319         mask[c++] |= diff(mHeight, points.mHeight);
320 
321     }
322 
getCenter(double p, int[] toUse, double[] data, float[] point, int offset)323     void getCenter(double p, int[] toUse, double[] data, float[] point, int offset) {
324         float v_x = mX;
325         float v_y = mY;
326         float v_width = mWidth;
327         float v_height = mHeight;
328         float translationX = 0, translationY = 0;
329         for (int i = 0; i < toUse.length; i++) {
330             float value = (float) data[i];
331 
332             switch (toUse[i]) {
333                 case OFF_X:
334                     v_x = value;
335                     break;
336                 case OFF_Y:
337                     v_y = value;
338                     break;
339                 case OFF_WIDTH:
340                     v_width = value;
341                     break;
342                 case OFF_HEIGHT:
343                     v_height = value;
344                     break;
345             }
346         }
347         if (mRelativeToController != null) {
348             float[] pos = new float[2];
349             float[] vel = new float[2];
350 
351             mRelativeToController.getCenter(p, pos, vel);
352             float rx = pos[0];
353             float ry = pos[1];
354             float radius = v_x;
355             float angle = v_y;
356             // TODO Debug angle
357             v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
358             v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
359         }
360 
361         point[offset] = v_x + v_width / 2 + translationX;
362         point[offset + 1] = v_y + v_height / 2 + translationY;
363     }
364 
getCenter(double p, int[] toUse, double[] data, float[] point, double[] vdata, float[] velocity)365     void getCenter(double p,
366             int[] toUse,
367             double[] data,
368             float[] point,
369             double[] vdata,
370             float[] velocity) {
371         float v_x = mX;
372         float v_y = mY;
373         float v_width = mWidth;
374         float v_height = mHeight;
375         float dv_x = 0;
376         float dv_y = 0;
377         float dv_width = 0;
378         float dv_height = 0;
379 
380         float translationX = 0, translationY = 0;
381         for (int i = 0; i < toUse.length; i++) {
382             float value = (float) data[i];
383             float dvalue = (float) vdata[i];
384 
385             switch (toUse[i]) {
386                 case OFF_X:
387                     v_x = value;
388                     dv_x = dvalue;
389                     break;
390                 case OFF_Y:
391                     v_y = value;
392                     dv_y = dvalue;
393                     break;
394                 case OFF_WIDTH:
395                     v_width = value;
396                     dv_width = dvalue;
397                     break;
398                 case OFF_HEIGHT:
399                     v_height = value;
400                     dv_height = dvalue;
401                     break;
402             }
403         }
404         float dpos_x = dv_x + dv_width / 2;
405         float dpos_y = dv_y + dv_height / 2;
406 
407         if (mRelativeToController != null) {
408             float[] pos = new float[2];
409             float[] vel = new float[2];
410             mRelativeToController.getCenter(p, pos, vel);
411             float rx = pos[0];
412             float ry = pos[1];
413             float radius = v_x;
414             float angle = v_y;
415             float dradius = dv_x;
416             float dangle = dv_y;
417             float drx = vel[0];
418             float dry = vel[1];
419             // TODO Debug angle
420             v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
421             v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
422             dpos_x = (float) (drx + dradius * Math.sin(angle) + Math.cos(angle) * dangle);
423             dpos_y = (float) (dry - dradius * Math.cos(angle) + Math.sin(angle) * dangle);
424         }
425 
426         point[0] = v_x + v_width / 2 + translationX;
427         point[1] = v_y + v_height / 2 + translationY;
428         velocity[0] = dpos_x;
429         velocity[1] = dpos_y;
430     }
431 
getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset)432     void getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset) {
433         float v_x = mX;
434         float v_y = mY;
435         float v_width = mWidth;
436         float v_height = mHeight;
437         float translationX = 0, translationY = 0;
438         for (int i = 0; i < toUse.length; i++) {
439             float value = (float) data[i];
440 
441             switch (toUse[i]) {
442                 case OFF_X:
443                     v_x = value;
444                     break;
445                 case OFF_Y:
446                     v_y = value;
447                     break;
448                 case OFF_WIDTH:
449                     v_width = value;
450                     break;
451                 case OFF_HEIGHT:
452                     v_height = value;
453                     break;
454             }
455         }
456         if (mRelativeToController != null) {
457             float[] pos = new float[2];
458             float[] vel = new float[2];
459             mRelativeToController.getCenter(p, pos, vel);
460             float rx = pos[0];
461             float ry = pos[1];
462             float radius = v_x;
463             float angle = v_y;
464             // TODO Debug angle
465             v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
466             v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
467         }
468 
469         point[offset] = v_x + v_width / 2 + translationX;
470         point[offset + 1] = v_y + v_height / 2 + translationY;
471     }
472 
getBounds(int[] toUse, double[] data, float[] point, int offset)473     void getBounds(int[] toUse, double[] data, float[] point, int offset) {
474         @SuppressWarnings("unused") float v_x = mX;
475         @SuppressWarnings("unused") float v_y = mY;
476         float v_width = mWidth;
477         float v_height = mHeight;
478         for (int i = 0; i < toUse.length; i++) {
479             float value = (float) data[i];
480 
481             switch (toUse[i]) {
482                 case OFF_X:
483                     v_x = value;
484                     break;
485                 case OFF_Y:
486                     v_y = value;
487                     break;
488                 case OFF_WIDTH:
489                     v_width = value;
490                     break;
491                 case OFF_HEIGHT:
492                     v_height = value;
493                     break;
494             }
495         }
496         point[offset] = v_width;
497         point[offset + 1] = v_height;
498     }
499 
500     double[] mTempValue = new double[18];
501     double[] mTempDelta = new double[18];
502 
503     // Called on the start Time Point
setView(float position, MotionWidget view, int[] toUse, double[] data, double[] slope, double[] cycle)504     void setView(float position,
505             MotionWidget view,
506             int[] toUse,
507             double[] data,
508             double[] slope,
509             double[] cycle) {
510         float v_x = mX;
511         float v_y = mY;
512         float v_width = mWidth;
513         float v_height = mHeight;
514         float dv_x = 0;
515         float dv_y = 0;
516         float dv_width = 0;
517         float dv_height = 0;
518         @SuppressWarnings("unused") float delta_path = 0;
519         float path_rotate = Float.NaN;
520         @SuppressWarnings("unused") String mod;
521 
522         if (toUse.length != 0 && mTempValue.length <= toUse[toUse.length - 1]) {
523             int scratch_data_length = toUse[toUse.length - 1] + 1;
524             mTempValue = new double[scratch_data_length];
525             mTempDelta = new double[scratch_data_length];
526         }
527         Arrays.fill(mTempValue, Double.NaN);
528         for (int i = 0; i < toUse.length; i++) {
529             mTempValue[toUse[i]] = data[i];
530             mTempDelta[toUse[i]] = slope[i];
531         }
532 
533         for (int i = 0; i < mTempValue.length; i++) {
534             if (Double.isNaN(mTempValue[i]) && (cycle == null || cycle[i] == 0.0)) {
535                 continue;
536             }
537             double deltaCycle = (cycle != null) ? cycle[i] : 0.0;
538             float value = (float) (Double.isNaN(mTempValue[i])
539                     ? deltaCycle : mTempValue[i] + deltaCycle);
540             float dvalue = (float) mTempDelta[i];
541 
542             switch (i) {
543                 case OFF_POSITION:
544                     delta_path = value;
545                     break;
546                 case OFF_X:
547                     v_x = value;
548                     dv_x = dvalue;
549 
550                     break;
551                 case OFF_Y:
552                     v_y = value;
553                     dv_y = dvalue;
554                     break;
555                 case OFF_WIDTH:
556                     v_width = value;
557                     dv_width = dvalue;
558                     break;
559                 case OFF_HEIGHT:
560                     v_height = value;
561                     dv_height = dvalue;
562                     break;
563                 case OFF_PATH_ROTATE:
564                     path_rotate = value;
565                     break;
566             }
567         }
568 
569         if (mRelativeToController != null) {
570             float[] pos = new float[2];
571             float[] vel = new float[2];
572 
573             mRelativeToController.getCenter(position, pos, vel);
574             float rx = pos[0];
575             float ry = pos[1];
576             float radius = v_x;
577             float angle = v_y;
578             float dradius = dv_x;
579             float dangle = dv_y;
580             float drx = vel[0];
581             float dry = vel[1];
582 
583             // TODO Debug angle
584             float pos_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
585             float pos_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
586             float dpos_x = (float) (drx + dradius * Math.sin(angle)
587                     + radius * Math.cos(angle) * dangle);
588             float dpos_y = (float) (dry - dradius * Math.cos(angle)
589                     + radius * Math.sin(angle) * dangle);
590             dv_x = dpos_x;
591             dv_y = dpos_y;
592             v_x = pos_x;
593             v_y = pos_y;
594             if (slope.length >= 2) {
595                 slope[0] = dpos_x;
596                 slope[1] = dpos_y;
597             }
598             if (!Float.isNaN(path_rotate)) {
599                 float rot = (float) (path_rotate + Math.toDegrees(Math.atan2(dv_y, dv_x)));
600                 view.setRotationZ(rot);
601             }
602 
603         } else {
604 
605             if (!Float.isNaN(path_rotate)) {
606                 float rot = 0;
607                 float dx = dv_x + dv_width / 2;
608                 float dy = dv_y + dv_height / 2;
609                 if (DEBUG) {
610                     Utils.log(TAG, "dv_x       =" + dv_x);
611                     Utils.log(TAG, "dv_y       =" + dv_y);
612                     Utils.log(TAG, "dv_width   =" + dv_width);
613                     Utils.log(TAG, "dv_height  =" + dv_height);
614                 }
615                 rot += (float) (path_rotate + Math.toDegrees(Math.atan2(dy, dx)));
616                 view.setRotationZ(rot);
617                 if (DEBUG) {
618                     Utils.log(TAG, "Rotated " + rot + "  = " + dx + "," + dy);
619                 }
620             }
621         }
622 
623         // Todo: develop a concept of Float layout in MotionWidget widget.layout(float ...)
624         int l = (int) (0.5f + v_x);
625         int t = (int) (0.5f + v_y);
626         int r = (int) (0.5f + v_x + v_width);
627         int b = (int) (0.5f + v_y + v_height);
628         int i_width = r - l;
629         int i_height = b - t;
630         if (OLD_WAY) { // This way may produce more stable with and height but risk gaps
631             l = (int) v_x;
632             t = (int) v_y;
633             i_width = (int) v_width;
634             i_height = (int) v_height;
635             r = l + i_width;
636             b = t + i_height;
637         }
638 
639         // MotionWidget must do Android View measure if layout changes
640         view.layout(l, t, r, b);
641         if (DEBUG) {
642             if (toUse.length > 0) {
643                 Utils.log(TAG, "setView " + mod);
644             }
645         }
646     }
647 
getRect(int[] toUse, double[] data, float[] path, int offset)648     void getRect(int[] toUse, double[] data, float[] path, int offset) {
649         float v_x = mX;
650         float v_y = mY;
651         float v_width = mWidth;
652         float v_height = mHeight;
653         @SuppressWarnings("unused") float delta_path = 0;
654         float rotation = 0;
655         @SuppressWarnings("unused") float alpha = 0;
656         @SuppressWarnings("unused") float rotationX = 0;
657         @SuppressWarnings("unused") float rotationY = 0;
658         float scaleX = 1;
659         float scaleY = 1;
660         float pivotX = Float.NaN;
661         float pivotY = Float.NaN;
662         float translationX = 0;
663         float translationY = 0;
664 
665         @SuppressWarnings("unused") String mod;
666 
667         for (int i = 0; i < toUse.length; i++) {
668             float value = (float) data[i];
669 
670             switch (toUse[i]) {
671                 case OFF_POSITION:
672                     delta_path = value;
673                     break;
674                 case OFF_X:
675                     v_x = value;
676                     break;
677                 case OFF_Y:
678                     v_y = value;
679                     break;
680                 case OFF_WIDTH:
681                     v_width = value;
682                     break;
683                 case OFF_HEIGHT:
684                     v_height = value;
685                     break;
686             }
687         }
688 
689         if (mRelativeToController != null) {
690             float rx = mRelativeToController.getCenterX();
691             float ry = mRelativeToController.getCenterY();
692             float radius = v_x;
693             float angle = v_y;
694             // TODO Debug angle
695             v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
696             v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
697         }
698 
699         float x1 = v_x;
700         float y1 = v_y;
701 
702         float x2 = v_x + v_width;
703         float y2 = y1;
704 
705         float x3 = x2;
706         float y3 = v_y + v_height;
707 
708         float x4 = x1;
709         float y4 = y3;
710 
711         float cx = x1 + v_width / 2;
712         float cy = y1 + v_height / 2;
713 
714         if (!Float.isNaN(pivotX)) {
715             cx = x1 + (x2 - x1) * pivotX;
716         }
717         if (!Float.isNaN(pivotY)) {
718 
719             cy = y1 + (y3 - y1) * pivotY;
720         }
721         if (scaleX != 1) {
722             float midx = (x1 + x2) / 2;
723             x1 = (x1 - midx) * scaleX + midx;
724             x2 = (x2 - midx) * scaleX + midx;
725             x3 = (x3 - midx) * scaleX + midx;
726             x4 = (x4 - midx) * scaleX + midx;
727         }
728         if (scaleY != 1) {
729             float midy = (y1 + y3) / 2;
730             y1 = (y1 - midy) * scaleY + midy;
731             y2 = (y2 - midy) * scaleY + midy;
732             y3 = (y3 - midy) * scaleY + midy;
733             y4 = (y4 - midy) * scaleY + midy;
734         }
735         if (rotation != 0) {
736             float sin = (float) Math.sin(Math.toRadians(rotation));
737             float cos = (float) Math.cos(Math.toRadians(rotation));
738             float tx1 = xRotate(sin, cos, cx, cy, x1, y1);
739             float ty1 = yRotate(sin, cos, cx, cy, x1, y1);
740             float tx2 = xRotate(sin, cos, cx, cy, x2, y2);
741             float ty2 = yRotate(sin, cos, cx, cy, x2, y2);
742             float tx3 = xRotate(sin, cos, cx, cy, x3, y3);
743             float ty3 = yRotate(sin, cos, cx, cy, x3, y3);
744             float tx4 = xRotate(sin, cos, cx, cy, x4, y4);
745             float ty4 = yRotate(sin, cos, cx, cy, x4, y4);
746             x1 = tx1;
747             y1 = ty1;
748             x2 = tx2;
749             y2 = ty2;
750             x3 = tx3;
751             y3 = ty3;
752             x4 = tx4;
753             y4 = ty4;
754         }
755 
756         x1 += translationX;
757         y1 += translationY;
758         x2 += translationX;
759         y2 += translationY;
760         x3 += translationX;
761         y3 += translationY;
762         x4 += translationX;
763         y4 += translationY;
764 
765         path[offset++] = x1;
766         path[offset++] = y1;
767         path[offset++] = x2;
768         path[offset++] = y2;
769         path[offset++] = x3;
770         path[offset++] = y3;
771         path[offset++] = x4;
772         path[offset++] = y4;
773     }
774 
775     /**
776      * mAnchorDpDt
777      */
setDpDt(float locationX, float locationY, float[] mAnchorDpDt, int[] toUse, double[] deltaData, double[] data)778     void setDpDt(float locationX,
779             float locationY,
780             float[] mAnchorDpDt,
781             int[] toUse,
782             double[] deltaData,
783             double[] data) {
784 
785         float d_x = 0;
786         float d_y = 0;
787         float d_width = 0;
788         float d_height = 0;
789 
790         float deltaScaleX = 0;
791         float deltaScaleY = 0;
792 
793         @SuppressWarnings("unused") float mPathRotate = Float.NaN;
794         float deltaTranslationX = 0;
795         float deltaTranslationY = 0;
796 
797         String mod = " dd = ";
798         for (int i = 0; i < toUse.length; i++) {
799             float deltaV = (float) deltaData[i];
800             if (DEBUG) {
801                 mod += " , D" + sNames[toUse[i]] + "/Dt= " + deltaV;
802             }
803             switch (toUse[i]) {
804                 case OFF_POSITION:
805                     break;
806                 case OFF_X:
807                     d_x = deltaV;
808                     break;
809                 case OFF_Y:
810                     d_y = deltaV;
811                     break;
812                 case OFF_WIDTH:
813                     d_width = deltaV;
814                     break;
815                 case OFF_HEIGHT:
816                     d_height = deltaV;
817                     break;
818 
819             }
820         }
821         if (DEBUG) {
822             if (toUse.length > 0) {
823                 Utils.log(TAG, "setDpDt " + mod);
824             }
825         }
826 
827         float deltaX = d_x - deltaScaleX * d_width / 2;
828         float deltaY = d_y - deltaScaleY * d_height / 2;
829         float deltaWidth = d_width * (1 + deltaScaleX);
830         float deltaHeight = d_height * (1 + deltaScaleY);
831         float deltaRight = deltaX + deltaWidth;
832         float deltaBottom = deltaY + deltaHeight;
833         if (DEBUG) {
834             if (toUse.length > 0) {
835 
836                 Utils.log(TAG, "D x /dt           =" + d_x);
837                 Utils.log(TAG, "D y /dt           =" + d_y);
838                 Utils.log(TAG, "D width /dt       =" + d_width);
839                 Utils.log(TAG, "D height /dt      =" + d_height);
840                 Utils.log(TAG, "D deltaScaleX /dt =" + deltaScaleX);
841                 Utils.log(TAG, "D deltaScaleY /dt =" + deltaScaleY);
842                 Utils.log(TAG, "D deltaX /dt      =" + deltaX);
843                 Utils.log(TAG, "D deltaY /dt      =" + deltaY);
844                 Utils.log(TAG, "D deltaWidth /dt  =" + deltaWidth);
845                 Utils.log(TAG, "D deltaHeight /dt =" + deltaHeight);
846                 Utils.log(TAG, "D deltaRight /dt  =" + deltaRight);
847                 Utils.log(TAG, "D deltaBottom /dt =" + deltaBottom);
848                 Utils.log(TAG, "locationX         =" + locationX);
849                 Utils.log(TAG, "locationY         =" + locationY);
850                 Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
851                 Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
852             }
853         }
854 
855         mAnchorDpDt[0] = deltaX * (1 - locationX) + deltaRight * locationX + deltaTranslationX;
856         mAnchorDpDt[1] = deltaY * (1 - locationY) + deltaBottom * locationY + deltaTranslationY;
857     }
858 
fillStandard(double[] data, int[] toUse)859     void fillStandard(double[] data, int[] toUse) {
860         float[] set = {mPosition, mX, mY, mWidth, mHeight, mPathRotate};
861         int c = 0;
862         for (int i = 0; i < toUse.length; i++) {
863             if (toUse[i] < set.length) {
864                 data[c++] = set[toUse[i]];
865             }
866         }
867     }
868 
hasCustomData(String name)869     boolean hasCustomData(String name) {
870         return mCustomAttributes.containsKey(name);
871     }
872 
getCustomDataCount(String name)873     int getCustomDataCount(String name) {
874         CustomVariable a = mCustomAttributes.get(name);
875         if (a == null) {
876             return 0;
877         }
878         return a.numberOfInterpolatedValues();
879     }
880 
getCustomData(String name, double[] value, int offset)881     int getCustomData(String name, double[] value, int offset) {
882         CustomVariable a = mCustomAttributes.get(name);
883         if (a == null) {
884             return 0;
885         } else if (a.numberOfInterpolatedValues() == 1) {
886             value[offset] = a.getValueToInterpolate();
887             return 1;
888         } else {
889             int n = a.numberOfInterpolatedValues();
890             float[] f = new float[n];
891             a.getValuesToInterpolate(f);
892             for (int i = 0; i < n; i++) {
893                 value[offset++] = f[i];
894             }
895             return n;
896         }
897     }
898 
setBounds(float x, float y, float w, float h)899     void setBounds(float x, float y, float w, float h) {
900         this.mX = x;
901         this.mY = y;
902         mWidth = w;
903         mHeight = h;
904     }
905 
906     @Override
compareTo(MotionPaths o)907     public int compareTo(MotionPaths o) {
908         return Float.compare(mPosition, o.mPosition);
909     }
910 
911     // @TODO: add description
applyParameters(MotionWidget c)912     public void applyParameters(MotionWidget c) {
913         MotionPaths point = this;
914         point.mKeyFrameEasing = Easing.getInterpolator(c.mMotion.mTransitionEasing);
915         point.mPathMotionArc = c.mMotion.mPathMotionArc;
916         point.mAnimateRelativeTo = c.mMotion.mAnimateRelativeTo;
917         point.mPathRotate = c.mMotion.mPathRotate;
918         point.mDrawPath = c.mMotion.mDrawPath;
919         point.mAnimateCircleAngleTo = c.mMotion.mAnimateCircleAngleTo;
920         point.mProgress = c.mPropertySet.mProgress;
921         if (c.mWidgetFrame != null && c.mWidgetFrame.widget != null) {
922             point.mRelativeAngle = c.mWidgetFrame.widget.mCircleConstraintAngle;
923         }
924 
925         Set<String> at = c.getCustomAttributeNames();
926         for (String s : at) {
927             CustomVariable attr = c.getCustomAttribute(s);
928             if (attr != null && attr.isContinuous()) {
929                 this.mCustomAttributes.put(s, attr);
930             }
931         }
932     }
933 
934     // @TODO: add description
configureRelativeTo(Motion toOrbit)935     public void configureRelativeTo(Motion toOrbit) {
936         @SuppressWarnings("unused") double[] p = toOrbit.getPos(mProgress); // get the position
937         // in the orbit
938     }
939 }
940