blob: 7ed687aa62d030e9c891b699281a17cfafeebfd2 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// 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.
#include "cobalt/renderer/smoothed_value.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace cobalt {
namespace renderer {
SmoothedValue::SmoothedValue(base::TimeDelta time_to_converge,
base::Optional<double> max_slope_magnitude)
: time_to_converge_(time_to_converge),
previous_derivative_(0),
max_slope_magnitude_(max_slope_magnitude) {
DCHECK(base::TimeDelta() < time_to_converge_);
DCHECK(!max_slope_magnitude_ || *max_slope_magnitude_ > 0);
}
void SmoothedValue::SetTarget(double target, const base::TimeTicks& time) {
// Determine the current derivative and value.
double current_derivative = GetCurrentDerivative(time);
base::Optional<double> current_value;
if (target_) {
current_value = GetValueAtTime(time);
}
// Set the previous derivative and value to the current derivative and value.
previous_derivative_ = current_derivative;
previous_value_ = current_value;
target_ = target;
target_set_time_ = time;
}
void SmoothedValue::SnapToTarget() {
previous_value_ = base::nullopt;
previous_derivative_ = 0;
}
double SmoothedValue::GetValueAtTime(const base::TimeTicks& time) const {
if (!previous_value_) {
// If only one target has ever been set, simply return it.
return *target_;
}
// Compute the current value based off of a cubic bezier curve.
double t = SmoothedValue::t(time);
double one_minus_t = 1 - t;
double P0 = SmoothedValue::P0();
double P1 = SmoothedValue::P1();
double P2 = SmoothedValue::P2();
double P3 = SmoothedValue::P3();
return one_minus_t * one_minus_t * one_minus_t * P0 +
3 * one_minus_t * one_minus_t * t * P1 + 3 * one_minus_t * t * t * P2 +
t * t * t * P3;
}
double SmoothedValue::t(const base::TimeTicks& time) const {
DCHECK(target_) << "SetTarget() must have been called previously.";
base::TimeDelta time_diff = time - target_set_time_;
double time_to_converge_in_seconds = time_to_converge_.InSecondsF();
// Enforce any maximum slope constraints (which can result in overriding the
// time to converge).
if (max_slope_magnitude_) {
double largest_slope = GetDerivativeWithLargestMagnitude();
if (largest_slope == std::numeric_limits<double>::infinity() ||
largest_slope == -std::numeric_limits<double>::infinity()) {
// If we can have a slope of infinity, then just don't move.
return 0;
}
// If we find that our smoothing curve's maximum slope would result in a
// slope greater than the maximum slope constraint, stretch the time to
// converge in order to meet the slope constraint. This can result in
// overriding the user-provided time to converge.
double unconstrained_largest_slope =
largest_slope / time_to_converge_in_seconds;
if (unconstrained_largest_slope < -*max_slope_magnitude_) {
time_to_converge_in_seconds = -largest_slope / *max_slope_magnitude_;
} else if (unconstrained_largest_slope > *max_slope_magnitude_) {
time_to_converge_in_seconds = largest_slope / *max_slope_magnitude_;
}
}
double t = time_diff.InSecondsF() / time_to_converge_in_seconds;
DCHECK_LE(0, t);
return std::max(std::min(t, 1.0), 0.0);
}
double SmoothedValue::P1() const {
// See comments in header for why P1() is calculated this way.
return *previous_value_ + previous_derivative_ / 3.0f;
}
double SmoothedValue::P2() const {
// See comments in header for why P2() is calculated this way.
return P3();
}
namespace {
double EvaluateCubicBezierDerivative(double P0, double P1, double P2, double P3,
double t) {
double one_minus_t = 1 - t;
return 3 * one_minus_t * one_minus_t * (P1 - P0) +
6 * one_minus_t * t * (P2 - P1) + 3 * t * t * (P3 - P2);
}
} // namespace
double SmoothedValue::GetCurrentDerivative(const base::TimeTicks& time) const {
if (!previous_value_) {
// If only one target has ever been set, return 0 as our derivative.
return 0;
}
double t = SmoothedValue::t(time);
double P0 = SmoothedValue::P0();
double P1 = SmoothedValue::P1();
double P2 = SmoothedValue::P2();
double P3 = SmoothedValue::P3();
return EvaluateCubicBezierDerivative(P0, P1, P2, P3, t);
}
double SmoothedValue::GetDerivativeWithLargestMagnitude() const {
double P0 = SmoothedValue::P0();
double P1 = SmoothedValue::P1();
double P2 = SmoothedValue::P2();
double P3 = SmoothedValue::P3();
// Since our spline is a cubic function, it will have a single inflection
// point where its derivative is 0 (or infinite if
// numerator = denominator = 0). This function finds that single inflection
// point and stores the value in |t|. We then evaluate the derivative at
// that inflection point, and at the beginning and end of the [0, 1] segment.
// We then compare the results and return the derivative with the largest
// magnitude.
// Compute the location of the inflection point by setting the second
// derivative to zero and solving.
double numerator = (P2 - 2 * P1 + P0);
double denominator = (-P3 + 3 * P2 - 3 * P1 + P0);
double t;
if (numerator == 0) {
t = 0;
} else if (denominator == 0.0) {
double numerator_sign = (numerator >= 0 ? 1.0 : -1.0);
return numerator_sign * std::numeric_limits<double>::infinity();
} else {
t = numerator / denominator;
}
// Evaluate the value of the derivative at each critical point.
double at_inflection_point = EvaluateCubicBezierDerivative(P0, P1, P2, P3, t);
double at_start = EvaluateCubicBezierDerivative(P0, P1, P2, P3, 0);
double at_end = EvaluateCubicBezierDerivative(P0, P1, P2, P3, 1);
if (std::abs(at_inflection_point) > std::abs(at_start)) {
if (std::abs(at_inflection_point) > std::abs(at_end)) {
return at_inflection_point;
} else {
return at_end;
}
} else {
if (std::abs(at_start) > std::abs(at_end)) {
return at_start;
} else {
return at_end;
}
}
}
} // namespace renderer
} // namespace cobalt