void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, ParentLayerCoord aAdditionalDelta, uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); if (aTimestampMs <= mVelocitySampleTimeMs + MIN_VELOCITY_SAMPLE_TIME_MS) { // See also the comment on MIN_VELOCITY_SAMPLE_TIME_MS. // We still update mPos so that the positioning is correct (and we don't run // into problems like bug 1042734) but the velocity will remain where it was. // In particular we don't update either mVelocitySampleTimeMs or // mVelocitySamplePos so that eventually when we do get an event with the // required time delta we use the corresponding distance delta as well. AXIS_LOG("%p|%s skipping velocity computation for small time delta %dms\n", mAsyncPanZoomController, Name(), (aTimestampMs - mVelocitySampleTimeMs)); mPos = aPos; return; } float newVelocity = mAxisLocked ? 0.0f : (float)(mVelocitySamplePos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mVelocitySampleTimeMs); newVelocity = ApplyFlingCurveToVelocity(newVelocity); AXIS_LOG("%p|%s updating velocity to %f with touch\n", mAsyncPanZoomController, Name(), newVelocity); mVelocity = newVelocity; mPos = aPos; mVelocitySampleTimeMs = aTimestampMs; mVelocitySamplePos = aPos; AddVelocityToQueue(aTimestampMs, mVelocity); }
void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, ParentLayerCoord aAdditionalDelta, uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); if (aTimestampMs <= mVelocitySampleTimeMs + MIN_VELOCITY_SAMPLE_TIME_MS) { // See also the comment on MIN_VELOCITY_SAMPLE_TIME_MS. // We still update mPos so that the positioning is correct (and we don't run // into problems like bug 1042734) but the velocity will remain where it was. // In particular we don't update either mVelocitySampleTimeMs or // mVelocitySamplePos so that eventually when we do get an event with the // required time delta we use the corresponding distance delta as well. AXIS_LOG("%p|%s skipping velocity computation for small time delta %dms\n", mAsyncPanZoomController, Name(), (aTimestampMs - mVelocitySampleTimeMs)); mPos = aPos; return; } float newVelocity = mAxisLocked ? 0.0f : (float)(mVelocitySamplePos - aPos + aAdditionalDelta) / (float)(aTimestampMs - mVelocitySampleTimeMs); if (gfxPrefs::APZMaxVelocity() > 0.0f) { bool velocityIsNegative = (newVelocity < 0); newVelocity = fabs(newVelocity); float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity()); newVelocity = std::min(newVelocity, maxVelocity); if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) { float curveThreshold = ToLocalVelocity(gfxPrefs::APZCurveThreshold()); if (newVelocity > curveThreshold) { // here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply the curve float scale = maxVelocity - curveThreshold; float funcInput = (newVelocity - curveThreshold) / scale; float funcOutput = gVelocityCurveFunction->GetValue(funcInput); float curvedVelocity = (funcOutput * scale) + curveThreshold; AXIS_LOG("%p|%s curving up velocity from %f to %f\n", mAsyncPanZoomController, Name(), newVelocity, curvedVelocity); newVelocity = curvedVelocity; } } if (velocityIsNegative) { newVelocity = -newVelocity; } } AXIS_LOG("%p|%s updating velocity to %f with touch\n", mAsyncPanZoomController, Name(), newVelocity); mVelocity = newVelocity; mPos = aPos; mVelocitySampleTimeMs = aTimestampMs; mVelocitySamplePos = aPos; // Limit queue size pased on pref mVelocityQueue.AppendElement(std::make_pair(aTimestampMs, mVelocity)); if (mVelocityQueue.Length() > gfxPrefs::APZMaxVelocityQueueSize()) { mVelocityQueue.RemoveElementAt(0); } }
void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); if (aTimestampMs == mPosTimeMs) { // This could be a duplicate event, or it could be a legitimate event // on some platforms that generate events really fast. As a compromise // update mPos so we don't run into problems like bug 1042734, even though // that means the velocity will be stale. Better than doing a divide-by-zero. mPos = aPos; return; } float newVelocity = mAxisLocked ? 0.0f : (float)(mPos - aPos) / (float)(aTimestampMs - mPosTimeMs); if (gfxPrefs::APZMaxVelocity() > 0.0f) { bool velocityIsNegative = (newVelocity < 0); newVelocity = fabs(newVelocity); float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity()); newVelocity = std::min(newVelocity, maxVelocity); if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) { float curveThreshold = ToLocalVelocity(gfxPrefs::APZCurveThreshold()); if (newVelocity > curveThreshold) { // here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply the curve float scale = maxVelocity - curveThreshold; float funcInput = (newVelocity - curveThreshold) / scale; float funcOutput = gVelocityCurveFunction->GetValue(funcInput); float curvedVelocity = (funcOutput * scale) + curveThreshold; AXIS_LOG("%p|%s curving up velocity from %f to %f\n", mAsyncPanZoomController, Name(), newVelocity, curvedVelocity); newVelocity = curvedVelocity; } } if (velocityIsNegative) { newVelocity = -newVelocity; } } AXIS_LOG("%p|%s updating velocity to %f with touch\n", mAsyncPanZoomController, Name(), newVelocity); mVelocity = newVelocity; mPos = aPos; mPosTimeMs = aTimestampMs; // Limit queue size pased on pref mVelocityQueue.AppendElement(std::make_pair(aTimestampMs, mVelocity)); if (mVelocityQueue.Length() > gfxPrefs::APZMaxVelocityQueueSize()) { mVelocityQueue.RemoveElementAt(0); } }
float Axis::ApplyFlingCurveToVelocity(float aVelocity) const { float newVelocity = aVelocity; if (gfxPrefs::APZMaxVelocity() > 0.0f) { bool velocityIsNegative = (newVelocity < 0); newVelocity = fabs(newVelocity); float maxVelocity = ToLocalVelocity(gfxPrefs::APZMaxVelocity()); newVelocity = std::min(newVelocity, maxVelocity); if (gfxPrefs::APZCurveThreshold() > 0.0f && gfxPrefs::APZCurveThreshold() < gfxPrefs::APZMaxVelocity()) { float curveThreshold = ToLocalVelocity(gfxPrefs::APZCurveThreshold()); if (newVelocity > curveThreshold) { // here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply the curve float scale = maxVelocity - curveThreshold; float funcInput = (newVelocity - curveThreshold) / scale; float funcOutput = gVelocityCurveFunction->GetValue(funcInput, ComputedTimingFunction::BeforeFlag::Unset); float curvedVelocity = (funcOutput * scale) + curveThreshold; AXIS_LOG("%p|%s curving up velocity from %f to %f\n", mAsyncPanZoomController, Name(), newVelocity, curvedVelocity); newVelocity = curvedVelocity; } } if (velocityIsNegative) { newVelocity = -newVelocity; } } return newVelocity; }
void Axis::CancelGesture() { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n", mAsyncPanZoomController, Name()); mVelocity = 0.0f; mVelocityQueue.Clear(); }
void Axis::CancelTouch() { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n", mAsyncPanZoomController, Name()); mVelocity = 0.0f; while (!mVelocityQueue.IsEmpty()) { mVelocityQueue.RemoveElementAt(0); } }
bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta, float aFriction, float aThreshold) { if (fabsf(mVelocity) <= aThreshold) { // If the velocity is very low, just set it to 0 and stop the fling, // otherwise we'll just asymptotically approach 0 and the user won't // actually see any changes. mVelocity = 0.0f; return false; } else { mVelocity *= pow(1.0f - aFriction, float(aDelta.ToMilliseconds())); } AXIS_LOG("%p|%s reduced velocity to %f due to friction\n", mAsyncPanZoomController, Name(), mVelocity); return true; }
bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) { mMSDModel.Simulate(aDelta); mOverscroll = mMSDModel.GetPosition(); if (mMSDModel.IsFinished(1.0)) { // "Jump" to the at-rest state. The jump shouldn't be noticeable as the // velocity and overscroll are already low. AXIS_LOG("%p|%s oscillation dropped below threshold, going to rest\n", mAsyncPanZoomController, Name()); ClearOverscroll(); mVelocity = 0; return false; } // Otherwise, continue the animation. return true; }
bool Axis::AdjustDisplacement(ParentLayerCoord aDisplacement, /* ParentLayerCoord */ float& aDisplacementOut, /* ParentLayerCoord */ float& aOverscrollAmountOut, bool forceOverscroll /* = false */) { if (mAxisLocked) { aOverscrollAmountOut = 0; aDisplacementOut = 0; return false; } if (forceOverscroll) { aOverscrollAmountOut = aDisplacement; aDisplacementOut = 0; return false; } StopSamplingOverscrollAnimation(); ParentLayerCoord displacement = aDisplacement; // First consume any overscroll in the opposite direction along this axis. ParentLayerCoord consumedOverscroll = 0; if (mOverscroll > 0 && aDisplacement < 0) { consumedOverscroll = std::min(mOverscroll, -aDisplacement); } else if (mOverscroll < 0 && aDisplacement > 0) { consumedOverscroll = 0.f - std::min(-mOverscroll, aDisplacement); } mOverscroll -= consumedOverscroll; displacement += consumedOverscroll; // Split the requested displacement into an allowed displacement that does // not overscroll, and an overscroll amount. aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement); if (aOverscrollAmountOut != 0.0f) { // No need to have a velocity along this axis anymore; it won't take us // anywhere, so we're just spinning needlessly. AXIS_LOG("%p|%s has overscrolled, clearing velocity\n", mAsyncPanZoomController, Name()); mVelocity = 0.0f; displacement -= aOverscrollAmountOut; } aDisplacementOut = displacement; return fabsf(consumedOverscroll) > EPSILON; }
void Axis::EndTouch(uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); mVelocity = 0; int count = 0; while (!mVelocityQueue.IsEmpty()) { uint32_t timeDelta = (aTimestampMs - mVelocityQueue[0].first); if (timeDelta < gfxPrefs::APZVelocityRelevanceTime()) { count++; mVelocity += mVelocityQueue[0].second; } mVelocityQueue.RemoveElementAt(0); } if (count > 1) { mVelocity /= count; } AXIS_LOG("%p|%s ending touch, computed velocity %f\n", mAsyncPanZoomController, Name(), mVelocity); }
bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) { // Short-circuit early rather than running through all the sampling code. if (mVelocity == 0.0f && mOverscroll == 0.0f) { return false; } // We approximate the curve traced out by the velocity of the spring // over time by breaking up the curve into small segments over which we // consider the velocity to be constant. If the animation is sampled // sufficiently often, then treating |aDelta| as a single segment of this // sort would be fine, but the frequency at which the animation is sampled // can be affected by external factors, and as the segment size grows larger, // the approximation gets worse and the approximated curve can even diverge // (i.e. oscillate forever, with displacements of increasing absolute value)! // To avoid this, we break up |aDelta| into smaller segments of length 1 ms // each, and a segment of any remaining fractional milliseconds. double milliseconds = aDelta.ToMilliseconds(); int wholeMilliseconds = (int) aDelta.ToMilliseconds(); double fractionalMilliseconds = milliseconds - wholeMilliseconds; for (int i = 0; i < wholeMilliseconds; ++i) { StepOverscrollAnimation(1); } StepOverscrollAnimation(fractionalMilliseconds); // If both the velocity and the displacement fall below a threshold, stop // the animation so we don't continue doing tiny oscillations that aren't // noticeable. if (fabs(mOverscroll) < gfxPrefs::APZOverscrollStopDistanceThreshold() && fabs(mVelocity) < gfxPrefs::APZOverscrollStopVelocityThreshold()) { // "Jump" to the at-rest state. The jump shouldn't be noticeable as the // velocity and overscroll are already low. AXIS_LOG("%p|%s oscillation dropped below threshold, going to rest\n", mAsyncPanZoomController, Name()); ClearOverscroll(); mVelocity = 0; return false; } // Otherwise, continue the animation. return true; }
void Axis::EndTouch(uint32_t aTimestampMs) { // mVelocityQueue is controller-thread only APZThreadUtils::AssertOnControllerThread(); mAxisLocked = false; mVelocity = 0; int count = 0; for (const auto& e : mVelocityQueue) { uint32_t timeDelta = (aTimestampMs - e.first); if (timeDelta < gfxPrefs::APZVelocityRelevanceTime()) { count++; mVelocity += e.second; } } mVelocityQueue.Clear(); if (count > 1) { mVelocity /= count; } AXIS_LOG("%p|%s ending touch, computed velocity %f\n", mAsyncPanZoomController, Name(), mVelocity); }
void Axis::SetVelocity(float aVelocity) { AXIS_LOG("%p|%s direct-setting velocity to %f\n", mAsyncPanZoomController, Name(), aVelocity); mVelocity = aVelocity; }
void Axis::StepOverscrollAnimation(double aStepDurationMilliseconds) { // Apply spring physics to the overscroll as time goes on. // Note: this method of sampling isn't perfectly smooth, as it assumes // a constant velocity over 'aDelta', instead of an accelerating velocity. // (The way we applying friction to flings has the same issue.) // Hooke's law with damping: // F = -kx - bv // where // k is a constant related to the stiffness of the spring // The larger the constant, the stiffer the spring. // x is the displacement of the end of the spring from its equilibrium // In our scenario, it's the amount of overscroll on the axis. // b is a constant that provides damping (friction) // v is the velocity of the point at the end of the spring // See http://gafferongames.com/game-physics/spring-physics/ const float kSpringStiffness = gfxPrefs::APZOverscrollSpringStiffness(); const float kSpringFriction = gfxPrefs::APZOverscrollSpringFriction(); // Apply spring force. float springForce = -1 * kSpringStiffness * mOverscroll; // Assume unit mass, so force = acceleration. float oldVelocity = mVelocity; mVelocity += springForce * aStepDurationMilliseconds; // Apply dampening. mVelocity *= pow(double(1 - kSpringFriction), aStepDurationMilliseconds); AXIS_LOG("%p|%s sampled overscroll animation, leaving velocity at %f\n", mAsyncPanZoomController, Name(), mVelocity); // At the peak of each oscillation, record new offset and scaling factors for // overscroll, to ensure that GetOverscroll always returns a value of the // same sign, and that this value is correctly adjusted as the spring is // dampened. bool velocitySignChange = (oldVelocity * mVelocity) < 0; if (mFirstOverscrollAnimationSample == 0.0f) { mFirstOverscrollAnimationSample = mOverscroll; // It's possible to start sampling overscroll with velocity == 0, or // velocity in the opposite direction of overscroll, so make sure we // correctly record the peak in this case. if (mOverscroll != 0 && ((mOverscroll > 0 ? oldVelocity : -oldVelocity) <= 0.0f)) { velocitySignChange = true; } } if (velocitySignChange) { bool oddOscillation = (mOverscroll.value * mFirstOverscrollAnimationSample.value) < 0.0f; mLastOverscrollPeak = oddOscillation ? mOverscroll : -mOverscroll; mOverscrollScale = 2.0f; } // Adjust the amount of overscroll based on the velocity. // Note that we allow for oscillations. mOverscroll += (mVelocity * aStepDurationMilliseconds); // Our mechanism for translating a set of mOverscroll values that oscillate // around zero to a set of GetOverscroll() values that have the same sign // (so content is always stretched, never compressed) assumes that // mOverscroll does not exceed mLastOverscrollPeak in magnitude. If our // calculations were exact, this would be the case, as a dampened spring // should never attain a displacement greater in magnitude than a previous // peak. In our approximation calculations, however, this may not hold // exactly. To ensure the assumption is not violated, we clamp the magnitude // of mOverscroll. if (mLastOverscrollPeak != 0 && fabs(mOverscroll) > fabs(mLastOverscrollPeak)) { mOverscroll = (mOverscroll >= 0) ? fabs(mLastOverscrollPeak) : -fabs(mLastOverscrollPeak); } }