content: /*
   * Copyright (C) 2021 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package androidx.constraintlayout.core.motion;
  
  import static androidx.constraintlayout.core.motion.MotionWidget.UNSET;
  
  import androidx.constraintlayout.core.motion.key.MotionKeyPosition;
  import androidx.constraintlayout.core.motion.utils.Easing;
  import androidx.constraintlayout.core.motion.utils.Utils;
  
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Set;
  
  /**
   * This is used to capture and play back path of the layout.
   * It is used to set the bounds of the view (view.layout(l, t, r, b))
   */
  public class MotionPaths implements Comparable<MotionPaths> {
      public static final String TAG = "MotionPaths";
      public static final boolean DEBUG = false;
      public static final boolean OLD_WAY = false; // the computes the positions the old way
      static final int OFF_POSITION = 0;
      static final int OFF_X = 1;
      static final int OFF_Y = 2;
      static final int OFF_WIDTH = 3;
      static final int OFF_HEIGHT = 4;
      static final int OFF_PATH_ROTATE = 5;
  
      // mode and type have same numbering scheme
      public static final int PERPENDICULAR = MotionKeyPosition.TYPE_PATH;
      public static final int CARTESIAN = MotionKeyPosition.TYPE_CARTESIAN;
      public static final int AXIS = MotionKeyPosition.TYPE_AXIS;
      public static final int SCREEN = MotionKeyPosition.TYPE_SCREEN;
      static String[] sNames = {"position", "x", "y", "width", "height", "pathRotate"};
      public String mId;
      Easing mKeyFrameEasing;
      int mDrawPath = 0;
      float mTime;
      float mPosition;
      float mX;
      float mY;
      float mWidth;
      float mHeight;
      float mPathRotate = Float.NaN;
      float mProgress = Float.NaN;
      int mPathMotionArc = UNSET;
      String mAnimateRelativeTo = null;
      float mRelativeAngle = Float.NaN;
      Motion mRelativeToController = null;
  
      HashMap<String, CustomVariable> mCustomAttributes = new HashMap<>();
      int mMode = 0; // how was this point computed 1=perpendicular 2=deltaRelative
      int mAnimateCircleAngleTo; // since angles loop there are 4 ways we can pic direction
  
      public MotionPaths() {
      }
  
      /**
       * set up with Cartesian
       */
      void initCartesian(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
          float position = c.mFramePosition / 100f;
          MotionPaths point = this;
          point.mTime = position;
  
          mDrawPath = c.mDrawPath;
          float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
          float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
          float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
          float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
  
          point.mPosition = point.mTime;
  
          float path = position; // the position on the path
  
          float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
          float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
          float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
          float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
          float pathVectorX = endCenterX - startCenterX;
          float pathVectorY = endCenterY - startCenterY;
          point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
          point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
          point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
  
          float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX;
          float dydx = Float.isNaN(c.mAltPercentY) ? 0 : c.mAltPercentY;
          float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY;
          float dxdy = Float.isNaN(c.mAltPercentX) ? 0 : c.mAltPercentX;
          point.mMode = MotionPaths.CARTESIAN;
          point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx
                  + pathVectorY * dxdy - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorX * dydx
                  + pathVectorY * dydy - scaleY * scaleHeight / 2);
  
          point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
          point.mPathMotionArc = c.mPathMotionArc;
      }
  
      /**
       * set up with Axis TODO check
       */
      void initAxis(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
          float position = c.mFramePosition / 100f;
          MotionPaths point = this;
          point.mTime = position;
  
          mDrawPath = c.mDrawPath;
          float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
          float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
          float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
          float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
  
          point.mPosition = point.mTime;
  
          float path = position; // the position on the path
  
          float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
          float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
          float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
          float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
          float pathVectorX = endCenterX - startCenterX;
          float pathVectorY = endCenterY - startCenterY;
          point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
          point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
          point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
  
          float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX;
          float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY;
  
          point.mMode = MotionPaths.AXIS;
          point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx
                   - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY
                  + pathVectorY * dydy - scaleY * scaleHeight / 2);
  
          point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
          point.mPathMotionArc = c.mPathMotionArc;
      }
  
      /**
       * takes the new keyPosition
       */
      public MotionPaths(int parentWidth,
              int parentHeight,
              MotionKeyPosition c,
              MotionPaths startTimePoint,
              MotionPaths endTimePoint) {
          if (startTimePoint.mAnimateRelativeTo != null) {
              initPolar(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
              return;
          }
          switch (c.mPositionType) {
              case MotionKeyPosition.TYPE_SCREEN:
                  initScreen(parentWidth, parentHeight, c, startTimePoint, endTimePoint);
                  return;
              case MotionKeyPosition.TYPE_PATH:
                  initPath(c, startTimePoint, endTimePoint);
                  return;
              default:
              case MotionKeyPosition.TYPE_CARTESIAN:
                  initCartesian(c, startTimePoint, endTimePoint);
                  return;
              case MotionKeyPosition.TYPE_AXIS:
                  initAxis(c, startTimePoint, endTimePoint);
                  return;
          }
      }
  
      void initPolar(int parentWidth,
              int parentHeight,
              MotionKeyPosition c,
              MotionPaths s,
              MotionPaths e) {
          float position = c.mFramePosition / 100f;
          this.mTime = position;
          mDrawPath = c.mDrawPath;
          this.mMode = c.mPositionType; // mode and type have same numbering scheme
          float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
          float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
          float scaleX = e.mWidth - s.mWidth;
          float scaleY = e.mHeight - s.mHeight;
          this.mPosition = this.mTime;
          mWidth = (int) (s.mWidth + scaleX * scaleWidth);
          mHeight = (int) (s.mHeight + scaleY * scaleHeight);
          @SuppressWarnings("unused") float startfactor = 1 - position;
          @SuppressWarnings("unused") float endfactor = position;
  
          switch (c.mPositionType) {
              case MotionKeyPosition.TYPE_SCREEN:
                  this.mX = Float.isNaN(c.mPercentX) ? (position * (e.mX - s.mX) + s.mX)
                          : c.mPercentX * Math.min(scaleHeight, scaleWidth);
                  this.mY = Float.isNaN(c.mPercentY)
                          ? (position * (e.mY - s.mY) + s.mY) : c.mPercentY;
                  break;
  
              case MotionKeyPosition.TYPE_PATH:
                  this.mX = (Float.isNaN(c.mPercentX)
                          ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
                  this.mY = (Float.isNaN(c.mPercentY)
                          ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
                  break;
              case MotionKeyPosition.TYPE_AXIS:
                  this.mX = (Float.isNaN(c.mPercentX)
                          ? position : c.mPercentX) * Math.abs(e.mX - s.mX) + Math.min(s.mX, e.mX);
                  this.mY = (Float.isNaN(c.mPercentY)
                          ? position : c.mPercentY) * Math.abs(e.mY - s.mY) + Math.min(s.mY, e.mY);
                  break;
              default:
              case MotionKeyPosition.TYPE_CARTESIAN:
                  this.mX = (Float.isNaN(c.mPercentX)
                          ? position : c.mPercentX) * (e.mX - s.mX) + s.mX;
                  this.mY = (Float.isNaN(c.mPercentY)
                          ? position : c.mPercentY) * (e.mY - s.mY) + s.mY;
                  break;
          }
  
          this.mAnimateRelativeTo = s.mAnimateRelativeTo;
          this.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
          this.mPathMotionArc = c.mPathMotionArc;
      }
  
      // @TODO: add description
      public void setupRelative(Motion mc, MotionPaths relative) {
          double dx = mX + mWidth / 2 - relative.mX - relative.mWidth / 2;
          double dy = mY + mHeight / 2 - relative.mY - relative.mHeight / 2;
          mRelativeToController = mc;
  
          mX = (float) Math.hypot(dy, dx);
          if (Float.isNaN(mRelativeAngle)) {
              mY = (float) (Math.atan2(dy, dx) + Math.PI / 2);
          } else {
              mY = (float) Math.toRadians(mRelativeAngle);
          }
      }
  
      void initScreen(int parentWidth,
              int parentHeight,
              MotionKeyPosition c,
              MotionPaths startTimePoint,
              MotionPaths endTimePoint) {
          float position = c.mFramePosition / 100f;
          MotionPaths point = this;
          point.mTime = position;
  
          mDrawPath = c.mDrawPath;
          float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
          float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
  
          float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
          float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
  
          point.mPosition = point.mTime;
  
          float path = position; // the position on the path
  
          float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
          float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
          float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
          float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
          float pathVectorX = endCenterX - startCenterX;
          float pathVectorY = endCenterY - startCenterY;
          point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
          point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
          point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
  
          point.mMode = MotionPaths.SCREEN;
          if (!Float.isNaN(c.mPercentX)) {
              parentWidth -= (int) point.mWidth;
              point.mX = (int) (c.mPercentX * parentWidth);
          }
          if (!Float.isNaN(c.mPercentY)) {
              parentHeight -= (int) point.mHeight;
              point.mY = (int) (c.mPercentY * parentHeight);
          }
  
          point.mAnimateRelativeTo = mAnimateRelativeTo;
          point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
          point.mPathMotionArc = c.mPathMotionArc;
      }
  
      void initPath(MotionKeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
  
          float position = c.mFramePosition / 100f;
          MotionPaths point = this;
          point.mTime = position;
  
          mDrawPath = c.mDrawPath;
          float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
          float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
  
          float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
          float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
  
          point.mPosition = point.mTime;
  
          float path = Float.isNaN(c.mPercentX) ? position : c.mPercentX; // the position on the path
  
          float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
          float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
          float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
          float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
          float pathVectorX = endCenterX - startCenterX;
          float pathVectorY = endCenterY - startCenterY;
          point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
          point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
          point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
          float perpendicular = Float.isNaN(c.mPercentY)
                  ? 0 : c.mPercentY; // the position on the path
          float perpendicularX = -pathVectorY;
          float perpendicularY = pathVectorX;
  
          float normalX = perpendicularX * perpendicular;
          float normalY = perpendicularY * perpendicular;
          point.mMode = MotionPaths.PERPENDICULAR;
          point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
          point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
          point.mX += normalX;
          point.mY += normalY;
  
          point.mAnimateRelativeTo = mAnimateRelativeTo;
          point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
          point.mPathMotionArc = c.mPathMotionArc;
      }
  
      private static float xRotate(float sin, float cos, float cx, float cy, float x, float y) {
          x = x - cx;
          y = y - cy;
          return x * cos - y * sin + cx;
      }
  
      private static float yRotate(float sin, float cos, float cx, float cy, float x, float y) {
          x = x - cx;
          y = y - cy;
          return x * sin + y * cos + cy;
      }
  
      private boolean diff(float a, float b) {
          if (Float.isNaN(a) || Float.isNaN(b)) {
              return Float.isNaN(a) != Float.isNaN(b);
          }
          return Math.abs(a - b) > 0.000001f;
      }
  
      void different(MotionPaths points, boolean[] mask, String[] custom, boolean arcMode) {
          int c = 0;
          boolean diffx = diff(mX, points.mX);
          boolean diffy = diff(mY, points.mY);
          mask[c++] |= diff(mPosition, points.mPosition);
          mask[c++] |= diffx || diffy || arcMode;
          mask[c++] |= diffx || diffy || arcMode;
          mask[c++] |= diff(mWidth, points.mWidth);
          mask[c++] |= diff(mHeight, points.mHeight);
  
      }
  
      void getCenter(double p, int[] toUse, double[] data, float[] point, int offset) {
          float v_x = mX;
          float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          float translationX = 0, translationY = 0;
          for (int i = 0; i < toUse.length; i++) {
              float value = (float) data[i];
  
              switch (toUse[i]) {
                  case OFF_X:
                      v_x = value;
                      break;
                  case OFF_Y:
                      v_y = value;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      break;
              }
          }
          if (mRelativeToController != null) {
              float[] pos = new float[2];
              float[] vel = new float[2];
  
              mRelativeToController.getCenter(p, pos, vel);
              float rx = pos[0];
              float ry = pos[1];
              float radius = v_x;
              float angle = v_y;
              // TODO Debug angle
              v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
              v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
          }
  
          point[offset] = v_x + v_width / 2 + translationX;
          point[offset + 1] = v_y + v_height / 2 + translationY;
      }
  
      void getCenter(double p,
              int[] toUse,
              double[] data,
              float[] point,
              double[] vdata,
              float[] velocity) {
          float v_x = mX;
          float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          float dv_x = 0;
          float dv_y = 0;
          float dv_width = 0;
          float dv_height = 0;
  
          float translationX = 0, translationY = 0;
          for (int i = 0; i < toUse.length; i++) {
              float value = (float) data[i];
              float dvalue = (float) vdata[i];
  
              switch (toUse[i]) {
                  case OFF_X:
                      v_x = value;
                      dv_x = dvalue;
                      break;
                  case OFF_Y:
                      v_y = value;
                      dv_y = dvalue;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      dv_width = dvalue;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      dv_height = dvalue;
                      break;
              }
          }
          float dpos_x = dv_x + dv_width / 2;
          float dpos_y = dv_y + dv_height / 2;
  
          if (mRelativeToController != null) {
              float[] pos = new float[2];
              float[] vel = new float[2];
              mRelativeToController.getCenter(p, pos, vel);
              float rx = pos[0];
              float ry = pos[1];
              float radius = v_x;
              float angle = v_y;
              float dradius = dv_x;
              float dangle = dv_y;
              float drx = vel[0];
              float dry = vel[1];
              // TODO Debug angle
              v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
              v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
              dpos_x = (float) (drx + dradius * Math.sin(angle) + Math.cos(angle) * dangle);
              dpos_y = (float) (dry - dradius * Math.cos(angle) + Math.sin(angle) * dangle);
          }
  
          point[0] = v_x + v_width / 2 + translationX;
          point[1] = v_y + v_height / 2 + translationY;
          velocity[0] = dpos_x;
          velocity[1] = dpos_y;
      }
  
      void getCenterVelocity(double p, int[] toUse, double[] data, float[] point, int offset) {
          float v_x = mX;
          float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          float translationX = 0, translationY = 0;
          for (int i = 0; i < toUse.length; i++) {
              float value = (float) data[i];
  
              switch (toUse[i]) {
                  case OFF_X:
                      v_x = value;
                      break;
                  case OFF_Y:
                      v_y = value;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      break;
              }
          }
          if (mRelativeToController != null) {
              float[] pos = new float[2];
              float[] vel = new float[2];
              mRelativeToController.getCenter(p, pos, vel);
              float rx = pos[0];
              float ry = pos[1];
              float radius = v_x;
              float angle = v_y;
              // TODO Debug angle
              v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
              v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
          }
  
          point[offset] = v_x + v_width / 2 + translationX;
          point[offset + 1] = v_y + v_height / 2 + translationY;
      }
  
      void getBounds(int[] toUse, double[] data, float[] point, int offset) {
          @SuppressWarnings("unused") float v_x = mX;
          @SuppressWarnings("unused") float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          for (int i = 0; i < toUse.length; i++) {
              float value = (float) data[i];
  
              switch (toUse[i]) {
                  case OFF_X:
                      v_x = value;
                      break;
                  case OFF_Y:
                      v_y = value;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      break;
              }
          }
          point[offset] = v_width;
          point[offset + 1] = v_height;
      }
  
      double[] mTempValue = new double[18];
      double[] mTempDelta = new double[18];
  
      // Called on the start Time Point
      void setView(float position,
              MotionWidget view,
              int[] toUse,
              double[] data,
              double[] slope,
              double[] cycle) {
          float v_x = mX;
          float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          float dv_x = 0;
          float dv_y = 0;
          float dv_width = 0;
          float dv_height = 0;
          @SuppressWarnings("unused") float delta_path = 0;
          float path_rotate = Float.NaN;
          @SuppressWarnings("unused") String mod;
  
          if (toUse.length != 0 && mTempValue.length <= toUse[toUse.length - 1]) {
              int scratch_data_length = toUse[toUse.length - 1] + 1;
              mTempValue = new double[scratch_data_length];
              mTempDelta = new double[scratch_data_length];
          }
          Arrays.fill(mTempValue, Double.NaN);
          for (int i = 0; i < toUse.length; i++) {
              mTempValue[toUse[i]] = data[i];
              mTempDelta[toUse[i]] = slope[i];
          }
  
          for (int i = 0; i < mTempValue.length; i++) {
              if (Double.isNaN(mTempValue[i]) && (cycle == null || cycle[i] == 0.0)) {
                  continue;
              }
              double deltaCycle = (cycle != null) ? cycle[i] : 0.0;
              float value = (float) (Double.isNaN(mTempValue[i])
                      ? deltaCycle : mTempValue[i] + deltaCycle);
              float dvalue = (float) mTempDelta[i];
  
              switch (i) {
                  case OFF_POSITION:
                      delta_path = value;
                      break;
                  case OFF_X:
                      v_x = value;
                      dv_x = dvalue;
  
                      break;
                  case OFF_Y:
                      v_y = value;
                      dv_y = dvalue;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      dv_width = dvalue;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      dv_height = dvalue;
                      break;
                  case OFF_PATH_ROTATE:
                      path_rotate = value;
                      break;
              }
          }
  
          if (mRelativeToController != null) {
              float[] pos = new float[2];
              float[] vel = new float[2];
  
              mRelativeToController.getCenter(position, pos, vel);
              float rx = pos[0];
              float ry = pos[1];
              float radius = v_x;
              float angle = v_y;
              float dradius = dv_x;
              float dangle = dv_y;
              float drx = vel[0];
              float dry = vel[1];
  
              // TODO Debug angle
              float pos_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
              float pos_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
              float dpos_x = (float) (drx + dradius * Math.sin(angle)
                      + radius * Math.cos(angle) * dangle);
              float dpos_y = (float) (dry - dradius * Math.cos(angle)
                      + radius * Math.sin(angle) * dangle);
              dv_x = dpos_x;
              dv_y = dpos_y;
              v_x = pos_x;
              v_y = pos_y;
              if (slope.length >= 2) {
                  slope[0] = dpos_x;
                  slope[1] = dpos_y;
              }
              if (!Float.isNaN(path_rotate)) {
                  float rot = (float) (path_rotate + Math.toDegrees(Math.atan2(dv_y, dv_x)));
                  view.setRotationZ(rot);
              }
  
          } else {
  
              if (!Float.isNaN(path_rotate)) {
                  float rot = 0;
                  float dx = dv_x + dv_width / 2;
                  float dy = dv_y + dv_height / 2;
                  if (DEBUG) {
                      Utils.log(TAG, "dv_x       =" + dv_x);
                      Utils.log(TAG, "dv_y       =" + dv_y);
                      Utils.log(TAG, "dv_width   =" + dv_width);
                      Utils.log(TAG, "dv_height  =" + dv_height);
                  }
                  rot += (float) (path_rotate + Math.toDegrees(Math.atan2(dy, dx)));
                  view.setRotationZ(rot);
                  if (DEBUG) {
                      Utils.log(TAG, "Rotated " + rot + "  = " + dx + "," + dy);
                  }
              }
          }
  
          // Todo: develop a concept of Float layout in MotionWidget widget.layout(float ...)
          int l = (int) (0.5f + v_x);
          int t = (int) (0.5f + v_y);
          int r = (int) (0.5f + v_x + v_width);
          int b = (int) (0.5f + v_y + v_height);
          int i_width = r - l;
          int i_height = b - t;
          if (OLD_WAY) { // This way may produce more stable with and height but risk gaps
              l = (int) v_x;
              t = (int) v_y;
              i_width = (int) v_width;
              i_height = (int) v_height;
              r = l + i_width;
              b = t + i_height;
          }
  
          // MotionWidget must do Android View measure if layout changes
          view.layout(l, t, r, b);
          if (DEBUG) {
              if (toUse.length > 0) {
                  Utils.log(TAG, "setView " + mod);
              }
          }
      }
  
      void getRect(int[] toUse, double[] data, float[] path, int offset) {
          float v_x = mX;
          float v_y = mY;
          float v_width = mWidth;
          float v_height = mHeight;
          @SuppressWarnings("unused") float delta_path = 0;
          float rotation = 0;
          @SuppressWarnings("unused") float alpha = 0;
          @SuppressWarnings("unused") float rotationX = 0;
          @SuppressWarnings("unused") float rotationY = 0;
          float scaleX = 1;
          float scaleY = 1;
          float pivotX = Float.NaN;
          float pivotY = Float.NaN;
          float translationX = 0;
          float translationY = 0;
  
          @SuppressWarnings("unused") String mod;
  
          for (int i = 0; i < toUse.length; i++) {
              float value = (float) data[i];
  
              switch (toUse[i]) {
                  case OFF_POSITION:
                      delta_path = value;
                      break;
                  case OFF_X:
                      v_x = value;
                      break;
                  case OFF_Y:
                      v_y = value;
                      break;
                  case OFF_WIDTH:
                      v_width = value;
                      break;
                  case OFF_HEIGHT:
                      v_height = value;
                      break;
              }
          }
  
          if (mRelativeToController != null) {
              float rx = mRelativeToController.getCenterX();
              float ry = mRelativeToController.getCenterY();
              float radius = v_x;
              float angle = v_y;
              // TODO Debug angle
              v_x = (float) (rx + radius * Math.sin(angle) - v_width / 2);
              v_y = (float) (ry - radius * Math.cos(angle) - v_height / 2);
          }
  
          float x1 = v_x;
          float y1 = v_y;
  
          float x2 = v_x + v_width;
          float y2 = y1;
  
          float x3 = x2;
          float y3 = v_y + v_height;
  
          float x4 = x1;
          float y4 = y3;
  
          float cx = x1 + v_width / 2;
          float cy = y1 + v_height / 2;
  
          if (!Float.isNaN(pivotX)) {
              cx = x1 + (x2 - x1) * pivotX;
          }
          if (!Float.isNaN(pivotY)) {
  
              cy = y1 + (y3 - y1) * pivotY;
          }
          if (scaleX != 1) {
              float midx = (x1 + x2) / 2;
              x1 = (x1 - midx) * scaleX + midx;
              x2 = (x2 - midx) * scaleX + midx;
              x3 = (x3 - midx) * scaleX + midx;
              x4 = (x4 - midx) * scaleX + midx;
          }
          if (scaleY != 1) {
              float midy = (y1 + y3) / 2;
              y1 = (y1 - midy) * scaleY + midy;
              y2 = (y2 - midy) * scaleY + midy;
              y3 = (y3 - midy) * scaleY + midy;
              y4 = (y4 - midy) * scaleY + midy;
          }
          if (rotation != 0) {
              float sin = (float) Math.sin(Math.toRadians(rotation));
              float cos = (float) Math.cos(Math.toRadians(rotation));
              float tx1 = xRotate(sin, cos, cx, cy, x1, y1);
              float ty1 = yRotate(sin, cos, cx, cy, x1, y1);
              float tx2 = xRotate(sin, cos, cx, cy, x2, y2);
              float ty2 = yRotate(sin, cos, cx, cy, x2, y2);
              float tx3 = xRotate(sin, cos, cx, cy, x3, y3);
              float ty3 = yRotate(sin, cos, cx, cy, x3, y3);
              float tx4 = xRotate(sin, cos, cx, cy, x4, y4);
              float ty4 = yRotate(sin, cos, cx, cy, x4, y4);
              x1 = tx1;
              y1 = ty1;
              x2 = tx2;
              y2 = ty2;
              x3 = tx3;
              y3 = ty3;
              x4 = tx4;
              y4 = ty4;
          }
  
          x1 += translationX;
          y1 += translationY;
          x2 += translationX;
          y2 += translationY;
          x3 += translationX;
          y3 += translationY;
          x4 += translationX;
          y4 += translationY;
  
          path[offset++] = x1;
          path[offset++] = y1;
          path[offset++] = x2;
          path[offset++] = y2;
          path[offset++] = x3;
          path[offset++] = y3;
          path[offset++] = x4;
          path[offset++] = y4;
      }
  
      /**
       * mAnchorDpDt
       */
      void setDpDt(float locationX,
              float locationY,
              float[] mAnchorDpDt,
              int[] toUse,
              double[] deltaData,
              double[] data) {
  
          float d_x = 0;
          float d_y = 0;
          float d_width = 0;
          float d_height = 0;
  
          float deltaScaleX = 0;
          float deltaScaleY = 0;
  
          @SuppressWarnings("unused") float mPathRotate = Float.NaN;
          float deltaTranslationX = 0;
          float deltaTranslationY = 0;
  
          String mod = " dd = ";
          for (int i = 0; i < toUse.length; i++) {
              float deltaV = (float) deltaData[i];
              if (DEBUG) {
                  mod += " , D" + sNames[toUse[i]] + "/Dt= " + deltaV;
              }
              switch (toUse[i]) {
                  case OFF_POSITION:
                      break;
                  case OFF_X:
                      d_x = deltaV;
                      break;
                  case OFF_Y:
                      d_y = deltaV;
                      break;
                  case OFF_WIDTH:
                      d_width = deltaV;
                      break;
                  case OFF_HEIGHT:
                      d_height = deltaV;
                      break;
  
              }
          }
          if (DEBUG) {
              if (toUse.length > 0) {
                  Utils.log(TAG, "setDpDt " + mod);
              }
          }
  
          float deltaX = d_x - deltaScaleX * d_width / 2;
          float deltaY = d_y - deltaScaleY * d_height / 2;
          float deltaWidth = d_width * (1 + deltaScaleX);
          float deltaHeight = d_height * (1 + deltaScaleY);
          float deltaRight = deltaX + deltaWidth;
          float deltaBottom = deltaY + deltaHeight;
          if (DEBUG) {
              if (toUse.length > 0) {
  
                  Utils.log(TAG, "D x /dt           =" + d_x);
                  Utils.log(TAG, "D y /dt           =" + d_y);
                  Utils.log(TAG, "D width /dt       =" + d_width);
                  Utils.log(TAG, "D height /dt      =" + d_height);
                  Utils.log(TAG, "D deltaScaleX /dt =" + deltaScaleX);
                  Utils.log(TAG, "D deltaScaleY /dt =" + deltaScaleY);
                  Utils.log(TAG, "D deltaX /dt      =" + deltaX);
                  Utils.log(TAG, "D deltaY /dt      =" + deltaY);
                  Utils.log(TAG, "D deltaWidth /dt  =" + deltaWidth);
                  Utils.log(TAG, "D deltaHeight /dt =" + deltaHeight);
                  Utils.log(TAG, "D deltaRight /dt  =" + deltaRight);
                  Utils.log(TAG, "D deltaBottom /dt =" + deltaBottom);
                  Utils.log(TAG, "locationX         =" + locationX);
                  Utils.log(TAG, "locationY         =" + locationY);
                  Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
                  Utils.log(TAG, "deltaTranslationX =" + deltaTranslationX);
              }
          }
  
          mAnchorDpDt[0] = deltaX * (1 - locationX) + deltaRight * locationX + deltaTranslationX;
          mAnchorDpDt[1] = deltaY * (1 - locationY) + deltaBottom * locationY + deltaTranslationY;
      }
  
      void fillStandard(double[] data, int[] toUse) {
          float[] set = {mPosition, mX, mY, mWidth, mHeight, mPathRotate};
          int c = 0;
          for (int i = 0; i < toUse.length; i++) {
              if (toUse[i] < set.length) {
                  data[c++] = set[toUse[i]];
              }
          }
      }
  
      boolean hasCustomData(String name) {
          return mCustomAttributes.containsKey(name);
      }
  
      int getCustomDataCount(String name) {
          CustomVariable a = mCustomAttributes.get(name);
          if (a == null) {
              return 0;
          }
          return a.numberOfInterpolatedValues();
      }
  
      int getCustomData(String name, double[] value, int offset) {
          CustomVariable a = mCustomAttributes.get(name);
          if (a == null) {
              return 0;
          } else if (a.numberOfInterpolatedValues() == 1) {
              value[offset] = a.getValueToInterpolate();
              return 1;
          } else {
              int n = a.numberOfInterpolatedValues();
              float[] f = new float[n];
              a.getValuesToInterpolate(f);
              for (int i = 0; i < n; i++) {
                  value[offset++] = f[i];
              }
              return n;
          }
      }
  
      void setBounds(float x, float y, float w, float h) {
          this.mX = x;
          this.mY = y;
          mWidth = w;
          mHeight = h;
      }
  
      @Override
      public int compareTo(MotionPaths o) {
          return Float.compare(mPosition, o.mPosition);
      }
  
      // @TODO: add description
      public void applyParameters(MotionWidget c) {
          MotionPaths point = this;
          point.mKeyFrameEasing = Easing.getInterpolator(c.mMotion.mTransitionEasing);
          point.mPathMotionArc = c.mMotion.mPathMotionArc;
          point.mAnimateRelativeTo = c.mMotion.mAnimateRelativeTo;
          point.mPathRotate = c.mMotion.mPathRotate;
          point.mDrawPath = c.mMotion.mDrawPath;
          point.mAnimateCircleAngleTo = c.mMotion.mAnimateCircleAngleTo;
          point.mProgress = c.mPropertySet.mProgress;
          if (c.mWidgetFrame != null && c.mWidgetFrame.widget != null) {
              point.mRelativeAngle = c.mWidgetFrame.widget.mCircleConstraintAngle;
          }
  
          Set<String> at = c.getCustomAttributeNames();
          for (String s : at) {
              CustomVariable attr = c.getCustomAttribute(s);
              if (attr != null && attr.isContinuous()) {
                  this.mCustomAttributes.put(s, attr);
              }
          }
      }
  
      // @TODO: add description
      public void configureRelativeTo(Motion toOrbit) {
          @SuppressWarnings("unused") double[] p = toOrbit.getPos(mProgress); // get the position
          // in the orbit
      }
  }
  