1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SMIL module.
16 : *
17 : * The Initial Developer of the Original Code is Brian Birtles.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Brian Birtles <birtles@gmail.com>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsSMILKeySpline.h"
39 : #include "prtypes.h"
40 : #include <math.h>
41 :
42 : #define NEWTON_ITERATIONS 4
43 : #define NEWTON_MIN_SLOPE 0.02
44 : #define SUBDIVISION_PRECISION 0.0000001
45 : #define SUBDIVISION_MAX_ITERATIONS 10
46 :
47 : const double nsSMILKeySpline::kSampleStepSize =
48 : 1.0 / double(kSplineTableSize - 1);
49 :
50 : void
51 0 : nsSMILKeySpline::Init(double aX1,
52 : double aY1,
53 : double aX2,
54 : double aY2)
55 : {
56 0 : mX1 = aX1;
57 0 : mY1 = aY1;
58 0 : mX2 = aX2;
59 0 : mY2 = aY2;
60 :
61 0 : if (mX1 != mY1 || mX2 != mY2)
62 0 : CalcSampleValues();
63 0 : }
64 :
65 : double
66 0 : nsSMILKeySpline::GetSplineValue(double aX) const
67 : {
68 0 : if (mX1 == mY1 && mX2 == mY2)
69 0 : return aX;
70 :
71 0 : return CalcBezier(GetTForX(aX), mY1, mY2);
72 : }
73 :
74 : void
75 0 : nsSMILKeySpline::GetSplineDerivativeValues(double aX, double& aDX, double& aDY) const
76 : {
77 0 : double t = GetTForX(aX);
78 0 : aDX = GetSlope(t, mX1, mX2);
79 0 : aDY = GetSlope(t, mY1, mY2);
80 0 : }
81 :
82 : void
83 0 : nsSMILKeySpline::CalcSampleValues()
84 : {
85 0 : for (PRUint32 i = 0; i < kSplineTableSize; ++i) {
86 0 : mSampleValues[i] = CalcBezier(double(i) * kSampleStepSize, mX1, mX2);
87 : }
88 0 : }
89 :
90 : /*static*/ double
91 0 : nsSMILKeySpline::CalcBezier(double aT,
92 : double aA1,
93 : double aA2)
94 : {
95 : // use Horner's scheme to evaluate the Bezier polynomial
96 0 : return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
97 : }
98 :
99 : /*static*/ double
100 0 : nsSMILKeySpline::GetSlope(double aT,
101 : double aA1,
102 : double aA2)
103 : {
104 0 : return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
105 : }
106 :
107 : double
108 0 : nsSMILKeySpline::GetTForX(double aX) const
109 : {
110 : // Find interval where t lies
111 0 : double intervalStart = 0.0;
112 0 : const double* currentSample = &mSampleValues[1];
113 0 : const double* const lastSample = &mSampleValues[kSplineTableSize - 1];
114 0 : for (; currentSample != lastSample && *currentSample <= aX;
115 : ++currentSample) {
116 0 : intervalStart += kSampleStepSize;
117 : }
118 0 : --currentSample; // t now lies between *currentSample and *currentSample+1
119 :
120 : // Interpolate to provide an initial guess for t
121 : double dist = (aX - *currentSample) /
122 0 : (*(currentSample+1) - *currentSample);
123 0 : double guessForT = intervalStart + dist * kSampleStepSize;
124 :
125 : // Check the slope to see what strategy to use. If the slope is too small
126 : // Newton-Raphson iteration won't converge on a root so we use bisection
127 : // instead.
128 0 : double initialSlope = GetSlope(guessForT, mX1, mX2);
129 0 : if (initialSlope >= NEWTON_MIN_SLOPE) {
130 0 : return NewtonRaphsonIterate(aX, guessForT);
131 0 : } else if (initialSlope == 0.0) {
132 0 : return guessForT;
133 : } else {
134 0 : return BinarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
135 : }
136 : }
137 :
138 : double
139 0 : nsSMILKeySpline::NewtonRaphsonIterate(double aX, double aGuessT) const
140 : {
141 : // Refine guess with Newton-Raphson iteration
142 0 : for (PRUint32 i = 0; i < NEWTON_ITERATIONS; ++i) {
143 : // We're trying to find where f(t) = aX,
144 : // so we're actually looking for a root for: CalcBezier(t) - aX
145 0 : double currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
146 0 : double currentSlope = GetSlope(aGuessT, mX1, mX2);
147 :
148 0 : if (currentSlope == 0.0)
149 0 : return aGuessT;
150 :
151 0 : aGuessT -= currentX / currentSlope;
152 : }
153 :
154 0 : return aGuessT;
155 : }
156 :
157 : double
158 0 : nsSMILKeySpline::BinarySubdivide(double aX, double aA, double aB) const
159 : {
160 : double currentX;
161 : double currentT;
162 0 : PRUint32 i = 0;
163 :
164 0 : do
165 : {
166 0 : currentT = aA + (aB - aA) / 2.0;
167 0 : currentX = CalcBezier(currentT, mX1, mX2) - aX;
168 :
169 0 : if (currentX > 0.0) {
170 0 : aB = currentT;
171 : } else {
172 0 : aA = currentT;
173 : }
174 0 : } while (fabs(currentX) > SUBDIVISION_PRECISION
175 : && ++i < SUBDIVISION_MAX_ITERATIONS);
176 :
177 0 : return currentT;
178 : }
|