/* * Copyright (C) 2010 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 com.replica.replicaisland; /** * Helper class for interpolating velocity over time given a target velocity and acceleration. * The current velocity will be accelerated towards the target until the target is reached. * Note that acceleration is effectively an absolute value--it always points in the direction of * the target velocity. */ public class Interpolator extends AllocationGuard { private float mCurrent; private float mTarget; private float mAcceleration; public Interpolator() { super(); } // Rather than simply interpolating acceleration and velocity for each time step // (as in, position += (velocity * time); velocity += (acceleration * time);), // we actually perform the work needed to calculate the integral of velocity with respect to // time. // // The integral of velocity is: // // integral[(v + aT)dT] // // Simplified to: // // vT + 1/2 * aT^2 // // Thus: // change in position = velocity * time + (0.5 * acceleration * (time^2)) // change in velocity = acceleration * time public void set(float current, float target, float acceleration) { mCurrent = current; mTarget = target; mAcceleration = acceleration; } // While this function writes directly to velocity, it doesn't affect // position. Instead, the position offset is returned so that it can be blended. public float interpolate(float secondsDelta) { float oldVelocity = mCurrent; // point the acceleration at the target, or zero it if we are already // there float directionalAcceleration = calculateAcceleration(oldVelocity, mAcceleration, mTarget); // calculate scaled acceleration (0.5 * acceleration * (time^2)) float scaledAcceleration; scaledAcceleration = scaleAcceleration(directionalAcceleration, secondsDelta); // calculate the change in position float positionOffset = (oldVelocity * secondsDelta) + scaledAcceleration; // change in velocity = v + aT float newVelocity = oldVelocity + (directionalAcceleration * secondsDelta); // check to see if we've passed our target velocity since the last time // step. If so, clamp to the target if (passedTarget(oldVelocity, newVelocity, mTarget)) { newVelocity = mTarget; } mCurrent = newVelocity; return positionOffset; } public float getCurrent() { return mCurrent; } private boolean passedTarget(float oldVelocity, float newVelocity, float targetVelocity) { boolean result = false; if (oldVelocity < targetVelocity && newVelocity > targetVelocity) { result = true; } else if (oldVelocity > targetVelocity && newVelocity < targetVelocity) { result = true; } return result; } // find the magnitude and direction of acceleration. // in this system, acceleration always points toward target velocity private float calculateAcceleration(float velocity, float acceleration, float target) { if (Math.abs(velocity - target) < 0.0001f) { // no accel needed acceleration = 0.0f; } else if (velocity > target) { // accel must be negative acceleration *= -1.0f; } return acceleration; } // calculates 1/2 aT^2 private float scaleAcceleration(float acceleration, float secondsDelta) { float timeSquared = (secondsDelta * secondsDelta); float scaledAccel = acceleration * timeSquared; scaledAccel *= 0.5f; return scaledAccel; } }