Path: blob/trunk/third_party/closure/goog/math/bezier.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.131415/**16* @fileoverview Represents a cubic Bezier curve.17*18* Uses the deCasteljau algorithm to compute points on the curve.19* http://en.wikipedia.org/wiki/De_Casteljau's_algorithm20*21* Currently it uses an unrolled version of the algorithm for speed. Eventually22* it may be useful to use the loop form of the algorithm in order to support23* curves of arbitrary degree.24*25* @author [email protected] (Robby Walker)26*/2728goog.provide('goog.math.Bezier');2930goog.require('goog.math');31goog.require('goog.math.Coordinate');32333435/**36* Object representing a cubic bezier curve.37* @param {number} x0 X coordinate of the start point.38* @param {number} y0 Y coordinate of the start point.39* @param {number} x1 X coordinate of the first control point.40* @param {number} y1 Y coordinate of the first control point.41* @param {number} x2 X coordinate of the second control point.42* @param {number} y2 Y coordinate of the second control point.43* @param {number} x3 X coordinate of the end point.44* @param {number} y3 Y coordinate of the end point.45* @struct46* @constructor47* @final48*/49goog.math.Bezier = function(x0, y0, x1, y1, x2, y2, x3, y3) {50/**51* X coordinate of the first point.52* @type {number}53*/54this.x0 = x0;5556/**57* Y coordinate of the first point.58* @type {number}59*/60this.y0 = y0;6162/**63* X coordinate of the first control point.64* @type {number}65*/66this.x1 = x1;6768/**69* Y coordinate of the first control point.70* @type {number}71*/72this.y1 = y1;7374/**75* X coordinate of the second control point.76* @type {number}77*/78this.x2 = x2;7980/**81* Y coordinate of the second control point.82* @type {number}83*/84this.y2 = y2;8586/**87* X coordinate of the end point.88* @type {number}89*/90this.x3 = x3;9192/**93* Y coordinate of the end point.94* @type {number}95*/96this.y3 = y3;97};9899100/**101* Constant used to approximate ellipses.102* See: http://canvaspaint.org/blog/2006/12/ellipse/103* @type {number}104*/105goog.math.Bezier.KAPPA = 4 * (Math.sqrt(2) - 1) / 3;106107108/**109* @return {!goog.math.Bezier} A copy of this curve.110*/111goog.math.Bezier.prototype.clone = function() {112return new goog.math.Bezier(113this.x0, this.y0, this.x1, this.y1, this.x2, this.y2, this.x3, this.y3);114};115116117/**118* Test if the given curve is exactly the same as this one.119* @param {goog.math.Bezier} other The other curve.120* @return {boolean} Whether the given curve is the same as this one.121*/122goog.math.Bezier.prototype.equals = function(other) {123return this.x0 == other.x0 && this.y0 == other.y0 && this.x1 == other.x1 &&124this.y1 == other.y1 && this.x2 == other.x2 && this.y2 == other.y2 &&125this.x3 == other.x3 && this.y3 == other.y3;126};127128129/**130* Modifies the curve in place to progress in the opposite direction.131*/132goog.math.Bezier.prototype.flip = function() {133var temp = this.x0;134this.x0 = this.x3;135this.x3 = temp;136temp = this.y0;137this.y0 = this.y3;138this.y3 = temp;139140temp = this.x1;141this.x1 = this.x2;142this.x2 = temp;143temp = this.y1;144this.y1 = this.y2;145this.y2 = temp;146};147148149/**150* Computes the curve's X coordinate at a point between 0 and 1.151* @param {number} t The point on the curve to find.152* @return {number} The computed coordinate.153*/154goog.math.Bezier.prototype.getPointX = function(t) {155// Special case start and end.156if (t == 0) {157return this.x0;158} else if (t == 1) {159return this.x3;160}161162// Step one - from 4 points to 3163var ix0 = goog.math.lerp(this.x0, this.x1, t);164var ix1 = goog.math.lerp(this.x1, this.x2, t);165var ix2 = goog.math.lerp(this.x2, this.x3, t);166167// Step two - from 3 points to 2168ix0 = goog.math.lerp(ix0, ix1, t);169ix1 = goog.math.lerp(ix1, ix2, t);170171// Final step - last point172return goog.math.lerp(ix0, ix1, t);173};174175176/**177* Computes the curve's Y coordinate at a point between 0 and 1.178* @param {number} t The point on the curve to find.179* @return {number} The computed coordinate.180*/181goog.math.Bezier.prototype.getPointY = function(t) {182// Special case start and end.183if (t == 0) {184return this.y0;185} else if (t == 1) {186return this.y3;187}188189// Step one - from 4 points to 3190var iy0 = goog.math.lerp(this.y0, this.y1, t);191var iy1 = goog.math.lerp(this.y1, this.y2, t);192var iy2 = goog.math.lerp(this.y2, this.y3, t);193194// Step two - from 3 points to 2195iy0 = goog.math.lerp(iy0, iy1, t);196iy1 = goog.math.lerp(iy1, iy2, t);197198// Final step - last point199return goog.math.lerp(iy0, iy1, t);200};201202203/**204* Computes the curve at a point between 0 and 1.205* @param {number} t The point on the curve to find.206* @return {!goog.math.Coordinate} The computed coordinate.207*/208goog.math.Bezier.prototype.getPoint = function(t) {209return new goog.math.Coordinate(this.getPointX(t), this.getPointY(t));210};211212213/**214* Changes this curve in place to be the portion of itself from [t, 1].215* @param {number} t The start of the desired portion of the curve.216*/217goog.math.Bezier.prototype.subdivideLeft = function(t) {218if (t == 1) {219return;220}221222// Step one - from 4 points to 3223var ix0 = goog.math.lerp(this.x0, this.x1, t);224var iy0 = goog.math.lerp(this.y0, this.y1, t);225226var ix1 = goog.math.lerp(this.x1, this.x2, t);227var iy1 = goog.math.lerp(this.y1, this.y2, t);228229var ix2 = goog.math.lerp(this.x2, this.x3, t);230var iy2 = goog.math.lerp(this.y2, this.y3, t);231232// Collect our new x1 and y1233this.x1 = ix0;234this.y1 = iy0;235236// Step two - from 3 points to 2237ix0 = goog.math.lerp(ix0, ix1, t);238iy0 = goog.math.lerp(iy0, iy1, t);239240ix1 = goog.math.lerp(ix1, ix2, t);241iy1 = goog.math.lerp(iy1, iy2, t);242243// Collect our new x2 and y2244this.x2 = ix0;245this.y2 = iy0;246247// Final step - last point248this.x3 = goog.math.lerp(ix0, ix1, t);249this.y3 = goog.math.lerp(iy0, iy1, t);250};251252253/**254* Changes this curve in place to be the portion of itself from [0, t].255* @param {number} t The end of the desired portion of the curve.256*/257goog.math.Bezier.prototype.subdivideRight = function(t) {258this.flip();259this.subdivideLeft(1 - t);260this.flip();261};262263264/**265* Changes this curve in place to be the portion of itself from [s, t].266* @param {number} s The start of the desired portion of the curve.267* @param {number} t The end of the desired portion of the curve.268*/269goog.math.Bezier.prototype.subdivide = function(s, t) {270this.subdivideRight(s);271this.subdivideLeft((t - s) / (1 - s));272};273274275/**276* Computes the position t of a point on the curve given its x coordinate.277* That is, for an input xVal, finds t s.t. getPointX(t) = xVal.278* As such, the following should always be true up to some small epsilon:279* t ~ solvePositionFromXValue(getPointX(t)) for t in [0, 1].280* @param {number} xVal The x coordinate of the point to find on the curve.281* @return {number} The position t.282*/283goog.math.Bezier.prototype.solvePositionFromXValue = function(xVal) {284// Desired precision on the computation.285var epsilon = 1e-6;286287// Initial estimate of t using linear interpolation.288var t = (xVal - this.x0) / (this.x3 - this.x0);289if (t <= 0) {290return 0;291} else if (t >= 1) {292return 1;293}294295// Try gradient descent to solve for t. If it works, it is very fast.296var tMin = 0;297var tMax = 1;298var value = 0;299for (var i = 0; i < 8; i++) {300value = this.getPointX(t);301var derivative = (this.getPointX(t + epsilon) - value) / epsilon;302if (Math.abs(value - xVal) < epsilon) {303return t;304} else if (Math.abs(derivative) < epsilon) {305break;306} else {307if (value < xVal) {308tMin = t;309} else {310tMax = t;311}312t -= (value - xVal) / derivative;313}314}315316// If the gradient descent got stuck in a local minimum, e.g. because317// the derivative was close to 0, use a Dichotomy refinement instead.318// We limit the number of interations to 8.319for (var i = 0; Math.abs(value - xVal) > epsilon && i < 8; i++) {320if (value < xVal) {321tMin = t;322t = (t + tMax) / 2;323} else {324tMax = t;325t = (t + tMin) / 2;326}327value = this.getPointX(t);328}329return t;330};331332333/**334* Computes the y coordinate of a point on the curve given its x coordinate.335* @param {number} xVal The x coordinate of the point on the curve.336* @return {number} The y coordinate of the point on the curve.337*/338goog.math.Bezier.prototype.solveYValueFromXValue = function(xVal) {339return this.getPointY(this.solvePositionFromXValue(xVal));340};341342343