dom/smil/nsSMILKeySpline.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/smil/nsSMILKeySpline.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,146 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "nsSMILKeySpline.h"
    1.10 +#include <stdint.h>
    1.11 +#include <math.h>
    1.12 +
    1.13 +#define NEWTON_ITERATIONS          4
    1.14 +#define NEWTON_MIN_SLOPE           0.02
    1.15 +#define SUBDIVISION_PRECISION      0.0000001
    1.16 +#define SUBDIVISION_MAX_ITERATIONS 10
    1.17 +
    1.18 +const double nsSMILKeySpline::kSampleStepSize =
    1.19 +                                        1.0 / double(kSplineTableSize - 1);
    1.20 +
    1.21 +void
    1.22 +nsSMILKeySpline::Init(double aX1,
    1.23 +                      double aY1,
    1.24 +                      double aX2,
    1.25 +                      double aY2)
    1.26 +{
    1.27 +  mX1 = aX1;
    1.28 +  mY1 = aY1;
    1.29 +  mX2 = aX2;
    1.30 +  mY2 = aY2;
    1.31 +
    1.32 +  if (mX1 != mY1 || mX2 != mY2)
    1.33 +    CalcSampleValues();
    1.34 +}
    1.35 +
    1.36 +double
    1.37 +nsSMILKeySpline::GetSplineValue(double aX) const
    1.38 +{
    1.39 +  if (mX1 == mY1 && mX2 == mY2)
    1.40 +    return aX;
    1.41 +
    1.42 +  return CalcBezier(GetTForX(aX), mY1, mY2);
    1.43 +}
    1.44 +
    1.45 +void
    1.46 +nsSMILKeySpline::GetSplineDerivativeValues(double aX, double& aDX, double& aDY) const
    1.47 +{
    1.48 +  double t = GetTForX(aX);
    1.49 +  aDX = GetSlope(t, mX1, mX2);
    1.50 +  aDY = GetSlope(t, mY1, mY2);
    1.51 +}
    1.52 +
    1.53 +void
    1.54 +nsSMILKeySpline::CalcSampleValues()
    1.55 +{
    1.56 +  for (uint32_t i = 0; i < kSplineTableSize; ++i) {
    1.57 +    mSampleValues[i] = CalcBezier(double(i) * kSampleStepSize, mX1, mX2);
    1.58 +  }
    1.59 +}
    1.60 +
    1.61 +/*static*/ double
    1.62 +nsSMILKeySpline::CalcBezier(double aT,
    1.63 +                            double aA1,
    1.64 +                            double aA2)
    1.65 +{
    1.66 +  // use Horner's scheme to evaluate the Bezier polynomial
    1.67 +  return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
    1.68 +}
    1.69 +
    1.70 +/*static*/ double
    1.71 +nsSMILKeySpline::GetSlope(double aT,
    1.72 +                          double aA1,
    1.73 +                          double aA2)
    1.74 +{
    1.75 +  return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
    1.76 +}
    1.77 +
    1.78 +double
    1.79 +nsSMILKeySpline::GetTForX(double aX) const
    1.80 +{
    1.81 +  // Find interval where t lies
    1.82 +  double intervalStart = 0.0;
    1.83 +  const double* currentSample = &mSampleValues[1];
    1.84 +  const double* const lastSample = &mSampleValues[kSplineTableSize - 1];
    1.85 +  for (; currentSample != lastSample && *currentSample <= aX;
    1.86 +        ++currentSample) {
    1.87 +    intervalStart += kSampleStepSize;
    1.88 +  }
    1.89 +  --currentSample; // t now lies between *currentSample and *currentSample+1
    1.90 +
    1.91 +  // Interpolate to provide an initial guess for t
    1.92 +  double dist = (aX - *currentSample) /
    1.93 +                (*(currentSample+1) - *currentSample);
    1.94 +  double guessForT = intervalStart + dist * kSampleStepSize;
    1.95 +
    1.96 +  // Check the slope to see what strategy to use. If the slope is too small
    1.97 +  // Newton-Raphson iteration won't converge on a root so we use bisection
    1.98 +  // instead.
    1.99 +  double initialSlope = GetSlope(guessForT, mX1, mX2);
   1.100 +  if (initialSlope >= NEWTON_MIN_SLOPE) {
   1.101 +    return NewtonRaphsonIterate(aX, guessForT);
   1.102 +  } else if (initialSlope == 0.0) {
   1.103 +    return guessForT;
   1.104 +  } else {
   1.105 +    return BinarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
   1.106 +  }
   1.107 +}
   1.108 +
   1.109 +double
   1.110 +nsSMILKeySpline::NewtonRaphsonIterate(double aX, double aGuessT) const
   1.111 +{
   1.112 +  // Refine guess with Newton-Raphson iteration
   1.113 +  for (uint32_t i = 0; i < NEWTON_ITERATIONS; ++i) {
   1.114 +    // We're trying to find where f(t) = aX,
   1.115 +    // so we're actually looking for a root for: CalcBezier(t) - aX
   1.116 +    double currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
   1.117 +    double currentSlope = GetSlope(aGuessT, mX1, mX2);
   1.118 +
   1.119 +    if (currentSlope == 0.0)
   1.120 +      return aGuessT;
   1.121 +
   1.122 +    aGuessT -= currentX / currentSlope;
   1.123 +  }
   1.124 +
   1.125 +  return aGuessT;
   1.126 +}
   1.127 +
   1.128 +double
   1.129 +nsSMILKeySpline::BinarySubdivide(double aX, double aA, double aB) const
   1.130 +{
   1.131 +  double currentX;
   1.132 +  double currentT;
   1.133 +  uint32_t i = 0;
   1.134 +
   1.135 +  do
   1.136 +  {
   1.137 +    currentT = aA + (aB - aA) / 2.0;
   1.138 +    currentX = CalcBezier(currentT, mX1, mX2) - aX;
   1.139 +
   1.140 +    if (currentX > 0.0) {
   1.141 +      aB = currentT;
   1.142 +    } else {
   1.143 +      aA = currentT;
   1.144 +    }
   1.145 +  } while (fabs(currentX) > SUBDIVISION_PRECISION
   1.146 +           && ++i < SUBDIVISION_MAX_ITERATIONS);
   1.147 +
   1.148 +  return currentT;
   1.149 +}

mercurial