我有一辆遵循预定义3D Bezier路径的3D汽车。我希望汽车的前轮旋转能够匹配汽车的变化方向。
我有想法将车轮的方向与路径方向的导数(3D矢量)相匹配,也就是贝塞尔路径的二阶导数。
由于某种原因,这几乎不起作用。在某些时候它似乎工作得很好,而在其他地方,轮子像地狱一样旋转。我注意到,即使Bezier路径是直线,二阶导数也会发生变化:在这种情况下,AFAIK应该 0 。
所以,我的第一个问题是我的想法是将轮子的旋转与第二度相匹配是要走的路。如果是,我的第二个问题究竟是什么问题?
这是我的Bezier 3D曲线代码:
package fanlib.math {
import flash.geom.Vector3D;
public class BezierCubic3D
{
public const anchor1:Vector3D = new Vector3D();
public const anchor2:Vector3D = new Vector3D();
public const control1:Vector3D = new Vector3D();
public const control2:Vector3D = new Vector3D();
/**
* Gets values from both 'getPointAt' and 'getDirectionAt'
*/
public const result:Vector3D = new Vector3D();
private const previous:Vector3D = new Vector3D(); // temporary (optimization)
// normalization aka arc-parameterization
public var arcLengths:Vector.<Number> = new Vector.<Number>;
public var steps:Number = 100;
private var _length:Number;
public function BezierCubic3D()
{
}
/**
* To get a point between anchor1 and anchor2, pass value [0...1]
* @param t
*/
public function getPointAt(t:Number):Vector3D {
const t2:Number = t*t;
const t3:Number = t*t2;
const threeT:Number = 3*t;
const threeT2:Number = 3*t2;
result.x = getPointAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, t3, threeT, threeT2);
result.y = getPointAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, t3, threeT, threeT2);
result.z = getPointAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, t3, threeT, threeT2);
return result;
}
public function getPointAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, t3:Number, threeT:Number, threeT2:Number):Number {
return t3 * (a2+3*(c1-c2)-a1) +
threeT2 * (a1-2*c1+c2) +
threeT * (c1-a1) +
a1;
}
/**
* @param t
* @return Un-normalized Vector3D!
*/
public function getDirectionAt(t:Number):Vector3D {
const threeT2:Number = 3 * t * t;
const sixT:Number = 6 * t;
result.x = getDirAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, threeT2, sixT);
result.y = getDirAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, threeT2, sixT);
result.z = getDirAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, threeT2, sixT);
return result;
}
public function getDirAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, threeT2:Number, sixT:Number):Number {
return threeT2 * (a2+3*(c1-c2)-a1) +
sixT * (a1-2*c1+c2) +
3 * (c1-a1);
}
public function getDirectionDerivativeAt(t:Number):Vector3D {
const sixT:Number = 6 * t;
result.x = getDirDerAxisAt(anchor1.x, anchor2.x, control1.x, control2.x, sixT);
result.y = getDirDerAxisAt(anchor1.y, anchor2.y, control1.y, control2.y, sixT);
result.z = getDirDerAxisAt(anchor1.z, anchor2.z, control1.z, control2.z, sixT);
return result;
}
public function getDirDerAxisAt(a1:Number,a2:Number,c1:Number,c2:Number, sixT:Number):Number {
return sixT * (a2+3*(c1-c2)-a1) +
6 * (a1-2*c1+c2);
}
/**
* Call this after any change to defining points and before accessing normalized points of curve.
*/
public function recalc():void {
arcLengths.length = steps + 1;
arcLengths[0] = 0;
const step:Number = 1 / steps;
previous.copyFrom(getPointAt(0));
_length = 0;
for (var i:int = 1; i <= steps; ++i) {
_length += Vector3D.distance(getPointAt(i * step), previous);
arcLengths[i] = _length;
previous.copyFrom(result);
}
}
/**
* 'recalc' must have already been called if any changes were made to any of the defining points
* @param u
* @return u normalized/converted to t
*/
public function normalizeT(u:Number):Number {
var targetLength:Number = u * arcLengths[steps];
var low:int = 0,
high:int = steps,
index:int; // TODO : have a look-up table of starting low/high indices for each step!
while (low < high) {
index = low + ((high - low) >>> 1);
if (arcLengths[index] < targetLength) {
low = index + 1;
} else {
high = index;
}
}
if (this.arcLengths[index] > targetLength) {
--index;
}
var lengthBefore:Number = arcLengths[index];
if (lengthBefore === targetLength) {
return index / steps;
} else {
return (index + (targetLength - lengthBefore) / (arcLengths[index + 1] - lengthBefore)) / steps;
}
}
public function getNormalizedPointAt(u:Number):Vector3D {
return getPointAt(normalizeT(u));
}
/**
* "Normalized" goes for t, not the return Vector3D!!!
* @param u
* @return Un-normalized Vector3D!
*/
public function getNormalizedDirectionAt(u:Number):Vector3D {
return getDirectionAt(normalizeT(u));
}
public function getNormalizedDirectionDerivativeAt(u:Number):Vector3D {
return getDirectionDerivativeAt(normalizeT(u));
}
public function get length():Number
{
return _length;
}
}
}
以下是将二度导数方向应用于汽车车轮的代码:
const dirDer:Vector3D = bezier.getDirectionDerivativeAt(time);
dirDer.negate(); // negate vector's values; for some reason, this gives better results
for each (wheel in dirWheels) {
wheel.setRotation(0,0,0); // must nullify before below line
const localDirDer:Vector3D = wheel.globalToLocalVector(dirDer); // convert dirDer vector to wheel's local axis; wheel translation does NOT affect conversion
wheel.setOrientation(localDirDer); // orients the object in a specific direction; Up-vector's default value = (0,1,0)
}
我甚至尝试过(无济于事):
for each (wheel in dirWheels) {
const localDirDer:Vector3D = wheel.parent.globalToLocalVector(dirDer); // convert dirDer vector to wheel's local axis; wheel translation does NOT affect conversion
wheel.setOrientation(localDirDer); // orients the object in a specific direction; Up-vector's default value = (0,1,0)
}
一个明显错误的例子:即使汽车在一条直线上,车轮原本是不旋转的(应该如此),但在汽车通过直线的中心点后,车轮旋转180°度!
修改 以下是Bezier 退化到一条直线的例子(所有4个点都属于一条直线)!因为,在直线的情况下,方向f'(t)是常数,它的导数f''(t)不应该总是为零?
例如,分别对于anchor1,anchor2,control1,control2:
Vector3D(-4.01,0.00,-1.90) Vector3D(4.01,0.00,-1.90)
Vector3D(-2.01,0.00,-1.90) Vector3D(2.01,0.00,-1.90)
我得到了
f'(0.08)=Vector3D(-1.00,0.00,0.00) f''(0.08)=Vector3D(10.14,0.00,0.00) f'(0.11)=Vector3D(-1.00,0.00,0.00) f''(0.11)=Vector3D(9.42,0.00,0.00) f'(0.15)=Vector3D(-1.00,0.00,0.00) f''(0.15)=Vector3D(8.44,0.00,0.00) f'(0.18)=Vector3D(-1.00,0.00,0.00) f''(0.18)=Vector3D(7.69,0.00,0.00) f'(0.21)=Vector3D(-1.00,0.00,0.00) f''(0.21)=Vector3D(6.87,0.00,0.00) f'(0.24)=Vector3D(-1.00,0.00,0.00) f''(0.24)=Vector3D(6.16,0.00,0.00) f'(0.27)=Vector3D(-1.00,0.00,0.00) f''(0.27)=Vector3D(5.47,0.00,0.00) f'(0.30)=Vector3D(-1.00,0.00,0.00) f''(0.30)=Vector3D(4.70,0.00,0.00) f'(0.33)=Vector3D(-1.00,0.00,0.00) f''(0.33)=Vector3D(4.03,0.00,0.00) f'(0.36)=Vector3D(-1.00,0.00,0.00) f''(0.36)=Vector3D(3.37,0.00,0.00) f'(0.39)=Vector3D(-1.00,0.00,0.00) f''(0.39)=Vector3D(2.63,0.00,0.00) f'(0.42)=Vector3D(-1.00,0.00,0.00) f''(0.42)=Vector3D(1.99,0.00,0.00) f'(0.44)=Vector3D(-1.00,0.00,0.00) f''(0.44)=Vector3D(1.34,0.00,0.00) f'(0.47)=Vector3D(-1.00,0.00,0.00) f''(0.47)=Vector3D(0.62,0.00,0.00) f'(0.50)=Vector3D(-1.00,0.00,0.00) f''(0.50)=Vector3D(-0.02,0.00,0.00) f'(0.53)=Vector3D(-1.00,0.00,0.00) f''(0.53)=Vector3D(-0.74,0.00,0.00) f'(0.56)=Vector3D(-1.00,0.00,0.00) f''(0.56)=Vector3D(-1.38,0.00,0.00) f'(0.58)=Vector3D(-1.00,0.00,0.00) f''(0.58)=Vector3D(-2.03,0.00,0.00) f'(0.61)=Vector3D(-1.00,0.00,0.00) f''(0.61)=Vector3D(-2.67,0.00,0.00) f'(0.64)=Vector3D(-1.00,0.00,0.00) f''(0.64)=Vector3D(-3.41,0.00,0.00) f'(0.67)=Vector3D(-1.00,0.00,0.00) f''(0.67)=Vector3D(-4.07,0.00,0.00) f'(0.70)=Vector3D(-1.00,0.00,0.00) f''(0.70)=Vector3D(-4.74,0.00,0.00) f'(0.73)=Vector3D(-1.00,0.00,0.00) f''(0.73)=Vector3D(-5.51,0.00,0.00) f'(0.76)=Vector3D(-1.00,0.00,0.00) f''(0.76)=Vector3D(-6.20,0.00,0.00) f'(0.79)=Vector3D(-1.00,0.00,0.00) f''(0.79)=Vector3D(-6.91,0.00,0.00) f'(0.82)=Vector3D(-1.00,0.00,0.00) f''(0.82)=Vector3D(-7.74,0.00,0.00) f'(0.85)=Vector3D(-1.00,0.00,0.00) f''(0.85)=Vector3D(-8.49,0.00,0.00) f'(0.89)=Vector3D(-1.00,0.00,0.00) f''(0.89)=Vector3D(-9.27,0.00,0.00) f'(0.92)=Vector3D(-1.00,0.00,0.00) f''(0.92)=Vector3D(-10.19,0.00,0.00) f'(0.96)=Vector3D(-1.00,0.00,0.00) f''(0.96)=Vector3D(-11.06,0.00,0.00) f'(1.00)=Vector3D(-1.00,0.00,0.00) f''(1.00)=Vector3D(-11.98,0.00,0.00)
答案 0 :(得分:1)
车轮相对于汽车方向的角度与路径的有符号曲率有关,通常用\kappa
表示。对于弧长参数化曲线,|\kappa| = length of vector dT/ds
其中T
是单位切向量,dT/ds
是其相对于弧长参数的导数。 \kappa
的符号取决于曲线的方向,但是一旦你弄清楚一个位置的左侧或右侧是否为正,你应该对场景的其余部分有所帮助。
贝塞尔曲线不是弧长参数化(除非你做了一些非常神奇的事情),所以你必须使用更复杂的表达式。对于飞机上的路径,您应该使用\kappa = (x'y''-y'x'')/(x'^2+y'^2)^{3/2}
。这很好,因为你不需要弧长参数化并且它也是有符号的,但你仍然需要弄清楚哪个符号意味着左或右。
您还必须弄清楚车轮角度与曲率之间的关系。为此,您可能会找到基于曲率半径 R = 1/\kappa
的公式。曲率半径具有很好的几何意义(与路径的“密切圆”相关),但当路径是直线时,曲率半径变为无穷大。
这是我在物理学文献中找到的关于车轮角度和曲率半径之间关系的一个近似公式:R = s/sqrt(2-2cos(2A))
其中s
是车轮基座(中心之间的距离)前轮和后轮)和A
是车轮的角度。您可以像A
,(s/R)^2/2 = 1-cos(2A)
,(s/(2R))^2 = sin^2(A)
,s\kappa/2 = sin(A)
,A = arcsin(s\kappa/2)
一样解决A=arcsin(s\kappa)
的公式。这很好地避免了0角度的奇点。像往常一样,你必须检查标志是否有意义并在必要时将其反转。
我见过的另一个公式是private async void Login_Tapped(object sender, TappedRoutedEventArgs e)
{
string str = user.Text; //get user name from textbox
HttpClient http = new HttpClient();
var response = await http.GetStringAsync("http://mywebpage.com/events/apis/user.php/user=" + str);
Class2 myClass2 = null;
try
{
var jsonSerializer = new DataContractJsonSerializer(typeof(Class2));
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response)))
{
myClass2 = jsonSerializer.ReadObject(stream) as Class2;
}
}
catch
{
// serialization error occurred
}
// do something with myClass2
}
。显然,这两个公式都不对。我不确定哪一个是对的。试试两者,或者在物理文献中找到一个好的治疗方法。
还有一件事需要考虑:在汽车的哪个位置测量曲率。同样,前轮或后轮有(至少)两种选择,我不确定哪个是正确的。我认为后轮。
如果这些选择都不成功,我可能犯了一个错误。让我知道,我会检查我的工作。