这个问题非常类似于:Quadratic bezier curve: Y coordinate for a given X?。但这个是立方体......
我正在使用getBezier函数计算贝塞尔曲线的Y坐标。贝塞尔曲线始终在(0,0)处开始,始终在(1,1)处结束。
我知道X值,所以我试图将其插入百分比(我是一个白痴)。但这显然不起作用。你能提供解决方案吗?这是必要的,它是一个白痴证明功能。像:
function yFromX (c2x,c2y,c3x,c3y) { //c1 = (0,0) and c4 = (1,1), domainc2 and domainc3 = [0,1]
//your magic
return y;
}
答案 0 :(得分:7)
由于问题是如此有限(函数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];
};
这肯定会破坏你的约束之外的一些输入。
答案 1 :(得分:4)
我使用this page中的算法并在JavaScript中写下来。它适用于我迄今为止测试的所有情况。 (并且不使用while
循环。)
调用solveCubicBezier函数。传入所有控制点的x值和要从中获取y坐标的x值。例如:
var results = solveCubicBezier(p0.x, p1.x, p2.x, p3.x, myX);
results
是一个数组,包含最初传递给Bezier函数的't'值。该数组可以包含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;
}
答案 2 :(得分:1)
原始答案already contains everything you need to know
有数字问题。立方体的确切解决方案存在稳定性问题。
典型贝塞尔曲线的平滑几何特性意味着空间搜索(递归细分)很好地收敛,它通常“足够快”,并且它很容易扩展到N维。 Qt源代码中有一个非常易读的实现。
答案 3 :(得分:1)
我已经用Java实现了这个解决方案并且工作得非常好。但最重要的是我理解它。谢谢!
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;
}
} }