1ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount/* 2ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Copyright (C) 2014 The Android Open Source Project 3ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 4ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Licensed under the Apache License, Version 2.0 (the "License"); 5ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * you may not use this file except in compliance with the License. 6ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * You may obtain a copy of the License at 7ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 8ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * http://www.apache.org/licenses/LICENSE-2.0 9ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 10ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Unless required by applicable law or agreed to in writing, software 11ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * distributed under the License is distributed on an "AS IS" BASIS, 12ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * See the License for the specific language governing permissions and 14ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * limitations under the License. 15ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 16ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountpackage android.transition; 17ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 18ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.content.Context; 19ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.content.res.TypedArray; 20ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.graphics.Path; 21ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountimport android.util.AttributeSet; 22ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 23eddc8dbed621077ddbc3a66f475920ea5f63283dGeorge Mountimport com.android.internal.R; 24eddc8dbed621077ddbc3a66f475920ea5f63283dGeorge Mount 25ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount/** 26ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * A PathMotion that generates a curved path along an arc on an imaginary circle containing 27ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * the two points. If the horizontal distance between the points is less than the vertical 28ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * distance, then the circle's center point will be horizontally aligned with the end point. If the 29ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * vertical distance is less than the horizontal distance then the circle's center point 30ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * will be vertically aligned with the end point. 31ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p> 32ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * When the two points are near horizontal or vertical, the curve of the motion will be 33ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * small as the center of the circle will be far from both points. To force curvature of 34ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * the path, {@link #setMinimumHorizontalAngle(float)} and 35ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * {@link #setMinimumVerticalAngle(float)} may be used to set the minimum angle of the 36ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * arc between two points. 37ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * </p> 38ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>This may be used in XML as an element inside a transition.</p> 3971fbb81b14958b80fe55738607740c6630e4e9daNeil Fuller * <pre>{@code 4071fbb81b14958b80fe55738607740c6630e4e9daNeil Fuller * <changeBounds> 4171fbb81b14958b80fe55738607740c6630e4e9daNeil Fuller * <arcMotion android:minimumHorizontalAngle="15" 42f9557619a7643c971e64e5b35583476202e77b7bGeorge Mount * android:minimumVerticalAngle="0" 43f9557619a7643c971e64e5b35583476202e77b7bGeorge Mount * android:maximumAngle="90"/> 4471fbb81b14958b80fe55738607740c6630e4e9daNeil Fuller * </changeBounds>} 45ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * </pre> 46ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 47ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mountpublic class ArcMotion extends PathMotion { 48ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 49ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private static final float DEFAULT_MIN_ANGLE_DEGREES = 0; 50ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private static final float DEFAULT_MAX_ANGLE_DEGREES = 70; 51ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private static final float DEFAULT_MAX_TANGENT = (float) 52ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount Math.tan(Math.toRadians(DEFAULT_MAX_ANGLE_DEGREES/2)); 53ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 54ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMinimumHorizontalAngle = 0; 55ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMinimumVerticalAngle = 0; 56ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMaximumAngle = DEFAULT_MAX_ANGLE_DEGREES; 57ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMinimumHorizontalTangent = 0; 58ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMinimumVerticalTangent = 0; 59ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private float mMaximumTangent = DEFAULT_MAX_TANGENT; 60ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 61ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public ArcMotion() {} 62ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 63ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public ArcMotion(Context context, AttributeSet attrs) { 64ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount super(context, attrs); 65ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ArcMotion); 66ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount float minimumVerticalAngle = a.getFloat(R.styleable.ArcMotion_minimumVerticalAngle, 67ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount DEFAULT_MIN_ANGLE_DEGREES); 68ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount setMinimumVerticalAngle(minimumVerticalAngle); 69ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount float minimumHorizontalAngle = a.getFloat(R.styleable.ArcMotion_minimumHorizontalAngle, 70ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount DEFAULT_MIN_ANGLE_DEGREES); 71ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount setMinimumHorizontalAngle(minimumHorizontalAngle); 72ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount float maximumAngle = a.getFloat(R.styleable.ArcMotion_maximumAngle, 73ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount DEFAULT_MAX_ANGLE_DEGREES); 74ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount setMaximumAngle(maximumAngle); 75ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount a.recycle(); 76ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 77ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 78ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 79ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Sets the minimum arc along the circle between two points aligned near horizontally. 80ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * When start and end points are close to horizontal, the calculated center point of the 81ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * circle will be far from both points, giving a near straight path between the points. 82ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * By setting a minimum angle, this forces the center point to be closer and give an 83ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * exaggerated curve to the path. 84ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 0.</p> 85ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 86ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @param angleInDegrees The minimum angle of the arc on a circle describing the Path 87ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * between two nearly horizontally-separated points. 88ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_minimumHorizontalAngle 89ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 90ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public void setMinimumHorizontalAngle(float angleInDegrees) { 91ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMinimumHorizontalAngle = angleInDegrees; 92ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMinimumHorizontalTangent = toTangent(angleInDegrees); 93ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 94ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 95ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 96ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Returns the minimum arc along the circle between two points aligned near horizontally. 97ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * When start and end points are close to horizontal, the calculated center point of the 98ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * circle will be far from both points, giving a near straight path between the points. 99ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * By setting a minimum angle, this forces the center point to be closer and give an 100ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * exaggerated curve to the path. 101ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 0.</p> 102ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 103ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @return The minimum arc along the circle between two points aligned near horizontally. 104ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_minimumHorizontalAngle 105ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 106ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public float getMinimumHorizontalAngle() { 107ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount return mMinimumHorizontalAngle; 108ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 109ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 110ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 111ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Sets the minimum arc along the circle between two points aligned near vertically. 112ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * When start and end points are close to vertical, the calculated center point of the 113ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * circle will be far from both points, giving a near straight path between the points. 114ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * By setting a minimum angle, this forces the center point to be closer and give an 115ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * exaggerated curve to the path. 116ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 0.</p> 117ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 118ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @param angleInDegrees The minimum angle of the arc on a circle describing the Path 119ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * between two nearly vertically-separated points. 120ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_minimumVerticalAngle 121ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 122ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public void setMinimumVerticalAngle(float angleInDegrees) { 123ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMinimumVerticalAngle = angleInDegrees; 124ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMinimumVerticalTangent = toTangent(angleInDegrees); 125ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 126ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 127ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 128ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Returns the minimum arc along the circle between two points aligned near vertically. 129ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * When start and end points are close to vertical, the calculated center point of the 130ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * circle will be far from both points, giving a near straight path between the points. 131ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * By setting a minimum angle, this forces the center point to be closer and give an 132ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * exaggerated curve to the path. 133ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 0.</p> 134ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 135ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @return The minimum angle of the arc on a circle describing the Path 136ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * between two nearly vertically-separated points. 137ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_minimumVerticalAngle 138ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 139ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public float getMinimumVerticalAngle() { 140ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount return mMinimumVerticalAngle; 141ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 142ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 143ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 144ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Sets the maximum arc along the circle between two points. When start and end points 145ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * have close to equal x and y differences, the curve between them is large. This forces 146ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * the curved path to have an arc of at most the given angle. 147ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 70 degrees.</p> 148ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 149ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @param angleInDegrees The maximum angle of the arc on a circle describing the Path 150ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * between the start and end points. 151ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_maximumAngle 152ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 153ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public void setMaximumAngle(float angleInDegrees) { 154ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMaximumAngle = angleInDegrees; 155ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount mMaximumTangent = toTangent(angleInDegrees); 156ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 157ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 158ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount /** 159ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * Returns the maximum arc along the circle between two points. When start and end points 160ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * have close to equal x and y differences, the curve between them is large. This forces 161ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * the curved path to have an arc of at most the given angle. 162ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * <p>The default value is 70 degrees.</p> 163ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * 164ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @return The maximum angle of the arc on a circle describing the Path 165ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * between the start and end points. 166ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount * @attr ref android.R.styleable#ArcMotion_maximumAngle 167ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount */ 168ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public float getMaximumAngle() { 169ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount return mMaximumAngle; 170ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 171ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 172ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount private static float toTangent(float arcInDegrees) { 173ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount if (arcInDegrees < 0 || arcInDegrees > 90) { 174ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount throw new IllegalArgumentException("Arc must be between 0 and 90 degrees"); 175ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 176ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount return (float) Math.tan(Math.toRadians(arcInDegrees / 2)); 177ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 178ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 179ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount @Override 180ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount public Path getPath(float startX, float startY, float endX, float endY) { 181ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // Here's a little ascii art to show how this is calculated: 182ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // c---------- b 183ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // \ / | 184ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // \ d | 185ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // \ / e 186ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // a----f 187ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // This diagram assumes that the horizontal distance is less than the vertical 188ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // distance between The start point (a) and end point (b). 189ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // d is the midpoint between a and b. c is the center point of the circle with 190ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // This path is formed by assuming that start and end points are in 191ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // an arc on a circle. The end point is centered in the circle vertically 192ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // and start is a point on the circle. 193ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 194ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // Triangles bfa and bde form similar right triangles. The control points 195ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount // for the cubic Bezier arc path are the midpoints between a and e and e and b. 196ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 197ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount Path path = new Path(); 198ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount path.moveTo(startX, startY); 199ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 200ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount float ex; 201ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount float ey; 2025de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float deltaX = endX - startX; 2035de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float deltaY = endY - startY; 2045de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss 2055de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // hypotenuse squared. 2065de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float h2 = deltaX * deltaX + deltaY * deltaY; 207ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2085de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // Midpoint between start and end 2095de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float dx = (startX + endX) / 2; 2105de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float dy = (startY + endY) / 2; 211ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2125de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // Distance squared between end point and mid point is (1/2 hypotenuse)^2 2135de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float midDist2 = h2 * 0.25f; 214ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2155de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float minimumArcDist2 = 0; 216ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2175de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss boolean isMovingUpwards = startY > endY; 218ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2195de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss if ((Math.abs(deltaX) < Math.abs(deltaY))) { 2205de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // Similar triangles bfa and bde mean that (ab/fb = eb/bd) 2215de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // Therefore, eb = ab * bd / fb 2225de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // ab = hypotenuse 2235de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // bd = hypotenuse/2 2245de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // fb = deltaY 2255de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float eDistY = Math.abs(h2 / (2 * deltaY)); 2265de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss if (isMovingUpwards) { 2275de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ey = endY + eDistY; 2285de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ex = endX; 229ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } else { 2305de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ey = startY + eDistY; 2315de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ex = startX; 2325de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } 233ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2345de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss minimumArcDist2 = midDist2 * mMinimumVerticalTangent 2355de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss * mMinimumVerticalTangent; 2365de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } else { 2375de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss // Same as above, but flip X & Y and account for negative eDist 2385de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float eDistX = h2 / (2 * deltaX); 2395de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss if (isMovingUpwards) { 2405de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ex = startX + eDistX; 2415de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ey = startY; 2425de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } else { 2435de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ex = endX - eDistX; 2445de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ey = endY; 245ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 246ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2475de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss minimumArcDist2 = midDist2 * mMinimumHorizontalTangent 2485de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss * mMinimumHorizontalTangent; 2495de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } 2505de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float arcDistX = dx - ex; 2515de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float arcDistY = dy - ey; 2525de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float arcDist2 = arcDistX * arcDistX + arcDistY * arcDistY; 253ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount 2545de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent; 2555de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss 2565de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float newArcDistance2 = 0; 2575de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss if (arcDist2 < minimumArcDist2) { 2585de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss newArcDistance2 = minimumArcDist2; 2595de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } else if (arcDist2 > maximumArcDist2) { 2605de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss newArcDistance2 = maximumArcDist2; 2615de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss } 2625de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss if (newArcDistance2 != 0) { 2635de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float ratio2 = newArcDistance2 / arcDist2; 2645de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float ratio = (float) Math.sqrt(ratio2); 2655de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ex = dx + (ratio * (ex - dx)); 2665de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss ey = dy + (ratio * (ey - dy)); 267ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 2685de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float control1X = (startX + ex) / 2; 2695de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float control1Y = (startY + ey) / 2; 2705de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float control2X = (ex + endX) / 2; 2715de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss float control2Y = (ey + endY) / 2; 2725de23c5eb4b56fdaa377fb7c098176441a17c7b4Ben Weiss path.cubicTo(control1X, control1Y, control2X, control2Y, endX, endY); 273ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount return path; 274ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount } 275ecd857be3946283ebb4306e2c03ae70f5c5bb152George Mount} 276