1 /* 2 * Copyright (C) 2014 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 17 package com.android.camera.ui.motion; 18 19 /** 20 * This models a value after the behavior of a spring. The value tracks the current value, a target 21 * value, and the current velocity and applies both a directional force and a damping force to the 22 * value on each update call. 23 */ 24 public class DampedSpring { 25 public static final float DEFAULT_TIME_TO_90_PERCENT_MILLIS = 200.0f; 26 public static final float DEFAULT_SPRING_STIFFNESS = 3.75f; 27 public static final float EPSILON = 0.01f; 28 29 private final float mSpringStiffness; 30 private final float mTimeTo90PercentMs; 31 32 private float mTarget = 0f; 33 private float mVelocity = 0f; 34 private float mValue = 0f; 35 DampedSpring()36 public DampedSpring() { 37 this(DEFAULT_TIME_TO_90_PERCENT_MILLIS, DEFAULT_SPRING_STIFFNESS); 38 } 39 DampedSpring(float timeTo90PercentMs)40 public DampedSpring(float timeTo90PercentMs) { 41 this(timeTo90PercentMs, DEFAULT_SPRING_STIFFNESS); 42 } 43 DampedSpring(float timeTo90PercentMs, float springStiffness)44 public DampedSpring(float timeTo90PercentMs, float springStiffness) { 45 // TODO: Assert timeTo90PercentMs >= 1ms, it might behave badly at low values. 46 // TODO: Assert springStiffness > 2.0f 47 48 mTimeTo90PercentMs = timeTo90PercentMs; 49 mSpringStiffness = springStiffness; 50 51 if (springStiffness > timeTo90PercentMs) { 52 throw new IllegalArgumentException("Creating a spring value with " 53 + "excessive stiffness will oscillate endlessly."); 54 } 55 } 56 57 /** 58 * @return the current value. 59 */ getValue()60 public float getValue() { 61 return mValue; 62 } 63 64 /** 65 * @param value the value to set this instance's current state too. 66 */ setValue(float value)67 public void setValue(float value) { 68 mValue = value; 69 } 70 71 /** 72 * @return the current target value. 73 */ getTarget()74 public float getTarget() { 75 return mTarget; 76 } 77 78 /** 79 * Set a target value. The current value will maintain any existing velocity values and will 80 * move towards the new target value. To forcibly stopAt the value use the stopAt() method. 81 * 82 * @param value the new value to move the current value towards. 83 */ setTarget(float value)84 public void setTarget(float value) { 85 mTarget = value; 86 } 87 88 /** 89 * Update the current value, moving it towards the actual value over the given 90 * time delta (in milliseconds) since the last update. This works off of the 91 * principle of a critically damped spring such that any given current value 92 * will move elastically towards the target value. The current value maintains 93 * and applies velocity, acceleration, and a damping force to give a continuous, 94 * smooth transition towards the target value. 95 * 96 * @param dtMs the time since the last update, or zero. 97 * @return the current value after the update occurs. 98 */ update(float dtMs)99 public float update(float dtMs) { 100 float dt = dtMs / mTimeTo90PercentMs; 101 float dts = dt * mSpringStiffness; 102 103 // If the dts > 1, and the velocity is zero, the force will exceed the 104 // distance to the target value and it will overshoot the value, causing 105 // weird behavior and unintended oscillation. since a critically damped 106 // spring should never overshoot the value, simply the current value to the 107 // target value. 108 if (dts > 1.0f || dts < 0.0f) { 109 stop(); 110 return mValue; 111 } 112 113 float delta = (mTarget - mValue); 114 float force = delta - 2.0f * mVelocity; 115 116 mVelocity += force * dts; 117 mValue += mVelocity * dts; 118 119 // If we get close enough to the actual value, simply set the current value 120 // to the current target value and stop. 121 if (!isActive()) { 122 stop(); 123 } 124 125 return mValue; 126 } 127 128 /** 129 * @return true if this instance has velocity or it is not at the target value. 130 */ isActive()131 public boolean isActive() { 132 boolean hasVelocity = Math.abs(mVelocity) >= EPSILON; 133 boolean atTarget = Math.abs(mTarget - mValue) < EPSILON; 134 return hasVelocity || !atTarget; 135 } 136 137 /** 138 * Stop the spring motion wherever it is currently at. Sets target to the 139 * current value and sets the velocity to zero. 140 */ 141 public void stop() { 142 mTarget = mValue; 143 mVelocity = 0.0f; 144 } 145 } 146