All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.scenario.animation.SplineInterpolator Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.scenario.animation;

import javafx.animation.Interpolator;

/**
 * An implementation of a spline interpolator for temporal interpolation that
 * tries to follow the specification referenced by:
 * http://www.w3.org/TR/SMIL/animation.html#animationNS-OverviewSpline .
 * 

* Basically, a cubic Bezier curve is created with start point (0,0) and * endpoint (1,1). The other two control points (px1, py1) and (px2, py2) are * given by the user, where px1, py1, px1, and px2 are all in the range [0,1]. A * property of this specially constrained Bezier curve is that it is strictly * monotonically increasing in both X and Y with t in range [0,1]. *

* The interpolator works by giving it a value for X. It then finds what * parameter t would generate this X value for the curve. Then this t parameter * is applied to the curve to solve for Y. As X increases from 0 to 1, t also * increases from 0 to 1, and correspondingly Y increases from 0 to 1. The * X-to-Y mapping is not a function of path/curve length. * */ public class SplineInterpolator extends Interpolator { /** * The coordinates of the 2 2D control points for a cubic Bezier curve, with * implicit start point (0,0) and end point (1,1) -- each individual * coordinate value must be in range [0,1]. */ private final double x1, y1, x2, y2; /** * Do the input control points form a line with (0,0) and (1,1), i.e., x1 == * y1 and x2 == y2 -- if so, then all x(t) == y(t) for the curve. */ private final boolean isCurveLinear; /** * Power of 2 sample size for lookup table of x values. */ private static final int SAMPLE_SIZE = 16; /** * Difference in t used to calculate each of the xSamples values -- power of * 2 sample size should provide exact representation of this value and its * integer multiples (integer in range of [0..SAMPLE_SIZE]. */ private static final double SAMPLE_INCREMENT = 1.0 / SAMPLE_SIZE; /** * X values for the bezier curve, sampled at increments of 1/SAMPLE_SIZE -- * this is used to find the good initial guess for parameter t, given an x. */ private final double[] xSamples = new double[SAMPLE_SIZE + 1]; /** * Creates a new instance with control points (0,0) (px1,py1) (px2,py2) * (1,1) -- px1, py1, px2, py2 all in range [0,1]. * * @param px1 * X coordinate of first control point, in range [0,1] * @param py1 * Y coordinate of first control point, in range [0,1] * @param px2 * X coordinate of second control point, in range [0,1] * @param py2 * Y coordinate of second control point, in range [0,1] */ public SplineInterpolator(double px1, double py1, double px2, double py2) { // check user input for precondition if (px1 < 0 || px1 > 1 || py1 < 0 || py1 > 1 || px2 < 0 || px2 > 1 || py2 < 0 || py2 > 1) { throw new IllegalArgumentException( "Control point coordinates must " + "all be in range [0,1]"); } // save control point data this.x1 = px1; this.y1 = py1; this.x2 = px2; this.y2 = py2; // calc linearity/identity curve isCurveLinear = ((x1 == y1) && (x2 == y2)); // make the array of x value samples if (!isCurveLinear) { for (int i = 0; i < SAMPLE_SIZE + 1; ++i) { xSamples[i] = eval(i * SAMPLE_INCREMENT, x1, x2); } } } public double getX1() { return x1; } public double getY1() { return y1; } public double getX2() { return x2; } public double getY2() { return y2; } @Override public int hashCode() { int hash = 7; hash = 19 * hash + (int) (Double.doubleToLongBits(this.x1) ^ (Double.doubleToLongBits(this.x1) >>> 32)); hash = 19 * hash + (int) (Double.doubleToLongBits(this.y1) ^ (Double.doubleToLongBits(this.y1) >>> 32)); hash = 19 * hash + (int) (Double.doubleToLongBits(this.x2) ^ (Double.doubleToLongBits(this.x2) >>> 32)); hash = 19 * hash + (int) (Double.doubleToLongBits(this.y2) ^ (Double.doubleToLongBits(this.y2) >>> 32)); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final SplineInterpolator other = (SplineInterpolator) obj; if (Double.doubleToLongBits(this.x1) != Double.doubleToLongBits(other.x1)) { return false; } if (Double.doubleToLongBits(this.y1) != Double.doubleToLongBits(other.y1)) { return false; } if (Double.doubleToLongBits(this.x2) != Double.doubleToLongBits(other.x2)) { return false; } if (Double.doubleToLongBits(this.y2) != Double.doubleToLongBits(other.y2)) { return false; } return true; } /** * Returns the y-value of the cubic bezier curve that corresponds to the x * input. * * @param x * is x-value of cubic bezier curve, in range [0,1] * @return corresponding y-value of cubic bezier curve -- in range [0,1] */ @Override public double curve(double x) { // check user input for precondition if (x < 0 || x > 1) { throw new IllegalArgumentException("x must be in range [0,1]"); } // check quick exit identity cases (linear curve or curve endpoints) if (isCurveLinear || x == 0 || x == 1) { return x; } // find the t parameter for a given x value, and use this t to calculate // the corresponding y value return eval(findTForX(x), y1, y2); } /** * Use Bernstein basis to evaluate 1D cubic Bezier curve (quicker and more * numerically stable than power basis) -- 1D control coordinates are (0, * p1, p2, 1), where p1 and p2 are in range [0,1], and there is no ordering * constraint on p1 and p2, i.e., p1 <= p2 does not have to be true. * * @param t * is the paramaterized value in range [0,1] * @param p1 * is 1st control point coordinate in range [0,1] * @param p2 * is 2nd control point coordinate in range [0,1] * @return the value of the Bezier curve at parameter t */ private double eval(double t, double p1, double p2) { // Use optimized version of the normal Bernstein basis form of Bezier: // (3*(1-t)*(1-t)*t*p1)+(3*(1-t)*t*t*p2)+(t*t*t), since p0=0, p3=1. // The above unoptimized version is best using -server, but since we // are probably doing client-side animation, this is faster. double compT = 1 - t; return t * (3 * compT * (compT * p1 + t * p2) + (t * t)); } /** * Evaluates Bernstein basis derivative of 1D cubic Bezier curve, where 1D * control points are (0, p1, p2, 1), where p1 and p2 are in range [0,1], * and there is no ordering constraint on p1 and p2, i.e., p1 <= p2 does * not have to be true. * * @param t * is the paramaterized value in range [0,1] * @param p1 * is 1st control point coordinate in range [0,1] * @param p2 * is 2nd control point coordinate in range [0,1] * @return the value of the Bezier curve at parameter t */ private double evalDerivative(double t, double p1, double p2) { // use optimized version of Berstein basis Bezier derivative: // (3*(1-t)*(1-t)*p1)+(6*(1-t)*t*(p2-p1))+(3*t*t*(1-p2)), since // p0=0 and p3=1. The above unoptimized version is best using -server, // but since we are probably doing client-side animation, this is // faster. double compT = 1 - t; return 3 * (compT * (compT * p1 + 2 * t * (p2 - p1)) + t * t * (1 - p2)); } /** * Find an initial good guess for what parameter t might produce the x-value * on the Bezier curve -- uses linear interpolation on the x-value sample * array that was created on construction. * * @param x * is x-value of cubic bezier curve, in range [0,1] * @return a good initial guess for parameter t (in range [0,1]) that gives * x */ private double getInitialGuessForT(double x) { // find which places in the array that x would be sandwiched between, // and then linearly interpolate a reasonable value of t -- array values // are ascending (or at least never descending) -- binary search is // probably more trouble than it is worth here for (int i = 1; i < SAMPLE_SIZE + 1; ++i) { if (xSamples[i] >= x) { double xRange = xSamples[i] - xSamples[i - 1]; if (xRange == 0) { // no change in value between samples, so use earlier time return (i - 1) * SAMPLE_INCREMENT; } else { // linearly interpolate the time value return ((i - 1) + ((x - xSamples[i - 1]) / xRange)) * SAMPLE_INCREMENT; } } } // shouldn't get here since 0 <= x <= 1, and xSamples[0] == 0 and // xSamples[SAMPLE_SIZE] == 1 (using power of 2 SAMPLE_SIZE for more // exact increment arithmetic) return 1; } /** * Finds the parameter t that produces the given x-value for the curve -- * uses Newton-Raphson to refine the value as opposed to subdividing until * we are within some tolerance. * * @param x * is x-value of cubic bezier curve, in range [0,1] * @return the parameter t (in range [0,1]) that produces x */ private double findTForX(double x) { // get an initial good guess for t double t = getInitialGuessForT(x); // use Newton-Raphson to refine the value for t -- for this constrained // Bezier with float accuracy (7 digits), any value not converged by 4 // iterations is cycling between values, which can minutely affect the // accuracy of the last digit final int numIterations = 4; for (int i = 0; i < numIterations; ++i) { // stop if this value of t gives us exactly x double xT = (eval(t, x1, x2) - x); if (xT == 0) { break; } // stop if derivative is 0 double dXdT = evalDerivative(t, x1, x2); if (dXdT == 0) { break; } // refine t t -= xT / dXdT; } return t; } @Override public String toString() { return "SplineInterpolator [x1=" + x1 + ", y1=" + y1 + ", x2=" + x2 + ", y2=" + y2 + "]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy