Home | History | Annotate | Download | only in motion
      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 
     36     public DampedSpring() {
     37         this(DEFAULT_TIME_TO_90_PERCENT_MILLIS, DEFAULT_SPRING_STIFFNESS);
     38     }
     39 
     40     public DampedSpring(float timeTo90PercentMs) {
     41         this(timeTo90PercentMs, DEFAULT_SPRING_STIFFNESS);
     42     }
     43 
     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      */
     60     public float getValue() {
     61         return mValue;
     62     }
     63 
     64     /**
     65      * @param value the value to set this instance's current state too.
     66      */
     67     public void setValue(float value) {
     68         mValue = value;
     69     }
     70 
     71     /**
     72      * @return the current target value.
     73      */
     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      */
     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      */
     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      */
    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