简体   繁体   English

给定x立方贝塞尔曲线的y坐标

[英]y coordinate for a given x cubic bezier

This question is very similar to: Quadratic bezier curve: Y coordinate for a given X? 这个问题非常类似于: 二次贝塞尔曲线:给定X的Y坐标? . But this one is cubic... 但这个是立方体......

I'm using the getBezier function to calculate the Y coordinates of a bezier curve. 我正在使用getBezier函数来计算贝塞尔曲线的Y坐标。 The bezier curve starts always at (0,0) and ends always at (1,1). 贝塞尔曲线始终在(0,0)处开始,始终在(1,1)处结束。

I know the X value, so I tried to insert it as percent (I'm a moron). 我知道X值,所以我试图将其插入百分比(我是一个白痴)。 But that didn't work, obviously. 但这显然不起作用。 Could you provide a solution? 你能提供解决方案吗? It's necessary it's an idiot proof function. 这是必要的,它是一个白痴证明功能。 Like: 喜欢:

function yFromX (c2x,c2y,c3x,c3y) { //c1 = (0,0) and c4 = (1,1), domainc2 and domainc3 = [0,1]
    //your magic
    return y;
}

Since the problem is so limited (function x(t) is monotonic), we can probably get away with using a pretty cheap method of solution-- binary search. 由于问题是如此有限(函数x(t)是单调的),我们可能会使用一种非常便宜的解决方法 - 二进制搜索。

var bezier = function(x0, y0, x1, y1, x2, y2, x3, y3, t) {
    /* whatever you're using to calculate points on the curve */
    return undefined; //I'll assume this returns array [x, y].
};

//we actually need a target x value to go with the middle control
//points, don't we? ;)
var yFromX = function(xTarget, x1, y1, x2, y2) {
  var xTolerance = 0.0001; //adjust as you please
  var myBezier = function(t) {
    return bezier(0, 0, x1, y1, x2, y2, 1, 1, t);
  };

  //we could do something less stupid, but since the x is monotonic
  //increasing given the problem constraints, we'll do a binary search.

  //establish bounds
  var lower = 0;
  var upper = 1;
  var percent = (upper + lower) / 2;

  //get initial x
  var x = myBezier(percent)[0];

  //loop until completion
  while(Math.abs(xTarget - x) > xTolerance) {
    if(xTarget > x) 
      lower = percent;
    else 
      upper = percent;

    percent = (upper + lower) / 2;
    x = myBezier(percent)[0];
  }
  //we're within tolerance of the desired x value.
  //return the y value.
  return myBezier(percent)[1];
};

This should certainly break on some inputs outside of your constraints. 这肯定会破坏你的约束之外的一些输入。

I used the algorithm from this page and wrote it down in JavaScript. 我使用了此页面中的算法并在JavaScript中将其写下来。 It works for all the cases I have tested so far. 它适用于我迄今为止测试的所有情况。 (And doesn't use a while loop.) (并且不使用while循环。)

Call the solveCubicBezier function. 调用solveCubicBezier函数。 Pass in the x values of all the control points and the x value you want to get the y coordinate from. 传入所有控制点的x值和要从中获取y坐标的x值。 For example: 例如:

var results = solveCubicBezier(p0.x, p1.x, p2.x, p3.x, myX);

results is an array containing the 't' values originally passed into the Bezier function. results是一个包含最初传递给Bezier函数的't'值的数组。 The array can contain 0 to 3 elements, because not all x values have a corresponding y value, and some even have multiple. 该数组可以包含0到3个元素,因为并非所有x值都具有相应的y值,有些甚至具有多个。

function solveQuadraticEquation(a, b, c) {

    var discriminant = b * b - 4 * a * c;

    if (discriminant < 0) {
        return [];

    } else {
        return [
            (-b + Math.sqrt(discriminant)) / (2 * a),
            (-b - Math.sqrt(discriminant)) / (2 * a)
        ];
    }

}

function solveCubicEquation(a, b, c, d) {

    if (!a) return solveQuadraticEquation(b, c, d);

    b /= a;
    c /= a;
    d /= a;

    var p = (3 * c - b * b) / 3;
    var q = (2 * b * b * b - 9 * b * c + 27 * d) / 27;

    if (p === 0) {
        return [ Math.pow(-q, 1 / 3) ];

    } else if (q === 0) {
        return [Math.sqrt(-p), -Math.sqrt(-p)];

    } else {

        var discriminant = Math.pow(q / 2, 2) + Math.pow(p / 3, 3);

        if (discriminant === 0) {
            return [Math.pow(q / 2, 1 / 3) - b / 3];

        } else if (discriminant > 0) {
            return [Math.pow(-(q / 2) + Math.sqrt(discriminant), 1 / 3) - Math.pow((q / 2) + Math.sqrt(discriminant), 1 / 3) - b / 3];

        } else {

            var r = Math.sqrt( Math.pow(-(p/3), 3) );
            var phi = Math.acos(-(q / (2 * Math.sqrt(Math.pow(-(p / 3), 3)))));

            var s = 2 * Math.pow(r, 1/3);

            return [
                s * Math.cos(phi / 3) - b / 3,
                s * Math.cos((phi + 2 * Math.PI) / 3) - b / 3,
                s * Math.cos((phi + 4 * Math.PI) / 3) - b / 3
            ];

        }

    }
}

function roundToDecimal(num, dec) {
    return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}

function solveCubicBezier(p0, p1, p2, p3, x) {

    p0 -= x;
    p1 -= x;
    p2 -= x;
    p3 -= x;

    var a = p3 - 3 * p2 + 3 * p1 - p0;
    var b = 3 * p2 - 6 * p1 + 3 * p0;
    var c = 3 * p1 - 3 * p0;
    var d = p0;

    var roots = solveCubicEquation(
        p3 - 3 * p2 + 3 * p1 - p0,
        3 * p2 - 6 * p1 + 3 * p0,
        3 * p1 - 3 * p0,
        p0
    );

    var result = [];
    var root;
    for (var i = 0; i < roots.length; i++) {
        root = roundToDecimal(roots[i], 15);
        if (root >= 0 && root <= 1) result.push(root);
    }

    return result;

}

I've implemented this solution in Java and works very fine. 我用Java实现了这个解决方案并且工作得非常好。 But the most important is that I understand it. 但最重要的是我理解它。 Thanks! 谢谢!

public class CubicBezier {
    private BezierCubic bezier = new BezierCubic();
    public CubicBezier(float x1, float y1, float x2, float y2) {
        bezier.set(new Vector3(0,0,0), new Vector3(x1,y1,0), new Vector3(x2,y2,0), new Vector3(1,1,1));
    }
    public float get(float t) {
        float l=0, u=1, s=(u+l)*0.5f;
        float x = bezier.getValueX(s);
        while (Math.abs(t-x) > 0.0001f) {
            if (t > x)  { l = s; }
            else        { u = s; }
            s = (u+l)*0.5f;
            x = bezier.getValueX(s);
        }
        return bezier.getValueY(s);
    }
};

public class BezierCubic {
private float[][] cpoints = new float[4][3];
private float[][] polinom = new float[4][3];

public BezierCubic() {}

public void set(Vector3 c0, Vector3 c1, Vector3 c2, Vector3 c3) {
    setPoint(0, c0);
    setPoint(1, c1);
    setPoint(2, c2);
    setPoint(3, c3);
    generate();
}

public float getValueX(float u) {
    return getValue(0, u);
}

public float getValueY(float u) {
    return getValue(1, u);
}

public float getValueZ(float u) {
    return getValue(2, u);
}

private float getValue(int i, float u) {
    return ((polinom[0][i]*u + polinom[1][i])*u + polinom[2][i])*u + polinom[3][i];
}

private void generate() {
    for (int i=0; i<3; i++) {
        float c0 = cpoints[0][i], c1 = cpoints[1][i], c2 = cpoints[2][i], c3 = cpoints[3][i];
        polinom[0][i] = c0 + 3*(c1 - c2) + c3;
        polinom[1][i] = 3*(c0 - 2*c1 + c2);
        polinom[2][i] = 3*(-c0 + c1);
        polinom[3][i] = c0;
    }
}

private void setPoint(int i, Vector3 v) {
    cpoints[i][0] = v.x;
    cpoints[i][1] = v.y;
    cpoints[i][2] = v.z;
}

} } }}

The original answer already contains everything you need to know 原始答案已包含您需要知道的所有内容

There are numerical issues. 有数字问题。 The exact solution for cubics suffers from stability problems. 立方体的精确解决方案存在稳定性问题。

The smooth geometric nature of typical Bezier curves means that spacial search (recursive subdivision) converges nicely, it's usually "fast enough", and it extends easily to N dimensions. 典型贝塞尔曲线的平滑几何特性意味着空间搜索(递归细分)很好地收敛,它通常“足够快”,并且它很容易扩展到N维。 There's quite a readable implementation in the Qt source code. Qt源代码中有相当可读的实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM