• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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