| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/events/gestures/physics_based_fling_curve.h" |
| |
| #include <algorithm> |
| |
| namespace { |
| |
| // These constants are defined based on UX experiment. |
| const float kDefaultP1X = 0.2f; |
| const float kDefaultP1Y = 1.0f; |
| const float kDefaultP2X = 0.55f; |
| const float kDefaultP2Y = 1.0f; |
| const float kDefaultPixelDeceleration = 2300.0f; |
| const float kMaxCurveDurationForFling = 2.0f; |
| const float kDefaultPhysicalDeceleration = 2.7559e-5f; // inch/ms^2. |
| |
| inline gfx::Vector2dF GetPositionAtTime(const gfx::Vector2dF& end_point, |
| double progress) { |
| return gfx::ScaleVector2d(end_point, progress); |
| } |
| |
| inline gfx::Vector2dF GetVelocityAtTime(const gfx::Vector2dF& current_offset, |
| const gfx::Vector2dF& prev_offset, |
| double delta) { |
| return gfx::ScaleVector2d(current_offset - prev_offset, (1 / delta)); |
| } |
| |
| float GetOffset(float velocity, float deceleration, float duration) { |
| float position = |
| (std::abs(velocity) - deceleration * duration * 0.5) * duration; |
| if (velocity < 0.0f) |
| return -position; |
| |
| return position; |
| } |
| |
| gfx::Vector2dF GetDecelerationInPixelsPerMs2( |
| const gfx::Vector2dF& pixels_per_inch) { |
| return gfx::ScaleVector2d(pixels_per_inch, kDefaultPhysicalDeceleration); |
| } |
| |
| gfx::Vector2dF GetDuration(const gfx::Vector2dF& velocity, |
| const gfx::Vector2dF& deceleration) { |
| return gfx::Vector2dF(fabs(velocity.x() / deceleration.x()), |
| fabs(velocity.y() / deceleration.y())); |
| } |
| |
| // Calculates the |distance_| based on the fling velocity. It utilizes |
| // following equation for |distance_| calculation. d = v*t - 0.5*a*t^2 where d |
| // = distance, v = initial velocity, a = acceleration and time = duration before |
| // it reaches zero velocity. time = final velocity(0) - initial velocity(v) / |
| // acceleration (a) This |distance_| is later used by PhysicsBasedFlingCurve to |
| // generate fling animation curve. |
| gfx::Vector2dF CalculateEndPoint(const gfx::Vector2dF& pixels_per_inch, |
| const gfx::Vector2dF& velocity_pixels_per_ms, |
| const gfx::Size& viewport) { |
| // deceleration is in pixels/ ms^2. |
| gfx::Vector2dF deceleration = GetDecelerationInPixelsPerMs2(pixels_per_inch); |
| |
| // duration is in ms |
| gfx::Vector2dF duration = GetDuration(velocity_pixels_per_ms, deceleration); |
| |
| // distance offset in screen coordinate |
| gfx::Vector2dF offset_in_screen_coord_space = gfx::Vector2dF( |
| GetOffset(velocity_pixels_per_ms.x(), deceleration.x(), duration.x()), |
| GetOffset(velocity_pixels_per_ms.y(), deceleration.y(), duration.y())); |
| |
| // Upper bound for the scroll distance for a fling |
| gfx::Vector2dF max_end_point = |
| gfx::Vector2dF(3 * viewport.width(), 3 * viewport.height()); |
| |
| if (std::abs(offset_in_screen_coord_space.x()) > max_end_point.x()) { |
| float sign = offset_in_screen_coord_space.x() > 0 ? 1 : -1; |
| offset_in_screen_coord_space.set_x(max_end_point.x() * sign); |
| } |
| |
| if (std::abs(offset_in_screen_coord_space.y()) > max_end_point.y()) { |
| float sign = offset_in_screen_coord_space.y() > 0 ? 1 : -1; |
| offset_in_screen_coord_space.set_y(max_end_point.y() * sign); |
| } |
| |
| return offset_in_screen_coord_space; |
| } |
| |
| } // namespace |
| |
| namespace ui { |
| |
| PhysicsBasedFlingCurve::PhysicsBasedFlingCurve( |
| const gfx::Vector2dF& velocity, |
| base::TimeTicks start_timestamp, |
| const gfx::Vector2dF& pixels_per_inch, |
| const gfx::Size& viewport) |
| : start_timestamp_(start_timestamp), |
| p1_(gfx::PointF(kDefaultP1X, kDefaultP1Y)), |
| p2_(gfx::PointF(kDefaultP2X, kDefaultP2Y)), |
| distance_(CalculateEndPoint(pixels_per_inch, |
| gfx::ScaleVector2d(velocity, 1 / 1000.0f), |
| viewport)), |
| curve_duration_(CalculateDurationAndConfigureControlPoints(velocity)), |
| bezier_(p1_.x(), p1_.y(), p2_.x(), p2_.y()), |
| previous_time_delta_(base::TimeDelta()) { |
| DCHECK(!velocity.IsZero()); |
| } |
| |
| PhysicsBasedFlingCurve::~PhysicsBasedFlingCurve() = default; |
| |
| bool PhysicsBasedFlingCurve::ComputeScrollOffset(base::TimeTicks time, |
| gfx::Vector2dF* offset, |
| gfx::Vector2dF* velocity) { |
| DCHECK(offset); |
| DCHECK(velocity); |
| base::TimeDelta elapsed_time = time - start_timestamp_; |
| if (elapsed_time < base::TimeDelta()) { |
| *offset = gfx::Vector2dF(); |
| *velocity = gfx::Vector2dF(); |
| return true; |
| } |
| |
| bool still_active = true; |
| double x = elapsed_time.InSecondsF() / curve_duration_; |
| if (x < 1.0f) { |
| double progress = bezier_.Solve(x); |
| *offset = GetPositionAtTime(distance_, progress); |
| *velocity = |
| GetVelocityAtTime(*offset, prev_offset_, |
| (elapsed_time - previous_time_delta_).InSecondsF()); |
| prev_offset_ = *offset; |
| previous_time_delta_ = elapsed_time; |
| still_active = true; |
| } else { |
| // At the end of animation, we should have travel distance equal to |
| // distance_ |
| *offset = distance_; |
| *velocity = gfx::Vector2dF(); |
| still_active = false; |
| } |
| |
| return still_active; |
| } |
| |
| // This method calculate the curve duration and generate the control points for |
| // bezier curve based on velocity and |distance_|. It calculate the slope based |
| // on the input velocity (initial velocity), curve duration and |distance_|. |
| // Slope is then used to configure the value of control points for curve. |
| float PhysicsBasedFlingCurve::CalculateDurationAndConfigureControlPoints( |
| const gfx::Vector2dF& velocity) { |
| float fling_velocity = std::max(fabs(velocity.x()), fabs(velocity.y())); |
| float duration = std::min(kMaxCurveDurationForFling, |
| (fling_velocity / kDefaultPixelDeceleration)); |
| float slope = fabs(velocity.y()) == fling_velocity |
| ? fabs(velocity.y() * duration / distance_.y()) |
| : fabs(velocity.x() * duration / distance_.x()); |
| |
| // Configure cubic bezier control points based on initial fling velocity. |
| // When matching the initial fling velocity for a Cubic Bezier Curve, scale |
| // |p1_.y| up until it reaches 1 After it reaches 1, move |p1_.x| to the left. |
| if (slope * p1_.x() < 1.0f) { |
| p1_.set_y(p1_.x() * slope); |
| } else { |
| p1_.set_x(p1_.y() / slope); |
| } |
| |
| return duration; |
| } |
| } // namespace ui |