好的,我一切正常,花了很长时间,所以我将在这里发布我的详细解决方案。
注意:所有代码示例均在JavaScript中。
因此,让我们将问题分解为基本部分:
您需要计算0..1
贝塞尔曲线的长度以及之间的点
现在,您需要调整比例,T
以将船从一种速度加速到另一种速度
正确设置贝塞尔曲线
找到一些绘制Bezier曲线的代码很容易,尽管有很多不同的方法,其中一种是DeCasteljau算法,但您也可以将等式用于三次Bézier曲线:
// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
有了这个,一个现在可以通过调用绘制贝塞尔曲线x
,并y
与t
从范围0 to 1
,让我们一起来看看:
呃...这不是真正的平均分配,是吗?
由于贝塞尔曲线的性质,上的点0...1
具有不同的arc lenghts
,因此靠近起点和终点的线段要比靠近曲线中间的线段长。
将T均匀地映射到曲线上AKA弧长参数化
那么该怎么办?那么简单来说,我们需要一个函数来我们的地图T
上的t
曲线,让我们T 0.25
的结果t
那是在25%
曲线的长度。
我们该怎么做?好吧,我们是Google ...但是事实证明,该术语不是可搜索的,有时您会点击此PDF。哪一种读物确实很棒,但是如果您已经忘记了在学校学到的所有数学知识(或者您只是不喜欢那些数学符号),那将毫无用处。
现在怎么办?好吧,去Google再读一些(阅读:6个小时),您终于找到了关于该主题的精彩文章(包括精美的图片!^ _ ^“):http :
//www.planetclegg.com/projects/WarpingTextToSplines.html
做实际的代码
如果您很久以前就已经失去了数学知识(并且您设法跳过了很棒的文章链接),尽管您无法拒绝下载PDF ,那么您现在可能会认为:“上帝,数百行代码和大量CPU”
不,不会。因为我们做所有程序员都会做的事,所以在数学方面:
我们只是作弊。
弧长参数化,惰性方法
面对现实吧,我们不需要游戏中无止境的精度,对吗?因此,除非您在美国国家航空航天局工作并计划向人们发送火星,否则您将不需要0.000001 pixel
完美的解决方案。
那么我们如何映射T
到t
?它很简单,仅包含3个步骤:
N
使用该位置计算曲线上的点t
,并将该位置的arc-length
(即曲线的长度)存储到数组中
要映射T
到t
,请先将T
曲线的总长度乘以得到u
,然后在长度数组中搜索小于的最大值的索引u
如果命中准确,则返回该索引处的数组值除以N
,如果未在找到的点与下一个点之间插入一点,则再次将其除以N
并返回。
就这样!现在,让我们看一下完整的代码:
function Bezier(a, b, c, d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.len = 100;
this.arcLengths = new Array(this.len + 1);
this.arcLengths[0] = 0;
var ox = this.x(0), oy = this.y(0), clen = 0;
for(var i = 1; i <= this.len; i += 1) {
var x = this.x(i * 0.05), y = this.y(i * 0.05);
var dx = ox - x, dy = oy - y;
clen += Math.sqrt(dx * dx + dy * dy);
this.arcLengths[i] = clen;
ox = x, oy = y;
}
this.length = clen;
}
这将初始化我们的新曲线并计算arg-lenghts
,它还将最后的长度存储为total length
曲线的,这里的关键因素this.len
就是我们的N
。映射越高,越精确,因为上面图片中的大小曲线100 points
似乎就足够了,如果您只需要一个很好的长度估计,那么像25
这样的事情已经可以完成,因为我们只需要1个像素例如,但是您的映射会不太精确,导致T
映射到时分布不均t
。
Bezier.prototype = {
map: function(u) {
var targetLength = u * this.arcLengths[this.len];
var low = 0, high = this.len, index = 0;
while (low < high) {
index = low + (((high - low) / 2) | 0);
if (this.arcLengths[index] < targetLength) {
low = index + 1;
} else {
high = index;
}
}
if (this.arcLengths[index] > targetLength) {
index--;
}
var lengthBefore = this.arcLengths[index];
if (lengthBefore === targetLength) {
return index / this.len;
} else {
return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
}
},
mx: function (u) {
return this.x(this.map(u));
},
my: function (u) {
return this.y(this.map(u));
},
实际的映射代码,首先我们binary search
对存储的长度进行简单查找,找到小于的最大长度targetLength
,然后返回或进行插值并返回。
x: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
+ 3 * ((1 - t) * (1 - t)) * t * this.b.x
+ 3 * (1 - t) * (t * t) * this.c.x
+ (t * t * t) * this.d.x;
},
y: function (t) {
return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
+ 3 * ((1 - t) * (1 - t)) * t * this.b.y
+ 3 * (1 - t) * (t * t) * this.c.y
+ (t * t * t) * this.d.y;
}
};
再次t
在曲线上计算。
取得成果的时间
到现在为止mx
,my
您将T
在曲线上均匀分布:)
不是那么难吗?再次证明,简单(尽管不是完美的解决方案)足以满足游戏需求。
如果您想查看完整的代码,可以使用Gist:https:
//gist.github.com/670236
最后,加速船只
因此,现在剩下的就是通过映射T
我们随后用来t
在曲线上找到位置的位置来加速船只沿其路径行驶。
首先,我们需要两个运动方程,即ut + 1/2at²
和(v - u) / t
在实际代码中,如下所示:
startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;
然后我们将其缩小为0...1
:
maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;
到了那里,船只现在正在沿着这条小路平稳行驶。
万一它不起作用...
当您阅读本文时,一切都很好,但我最初在加速部分遇到了一些问题,当向Gamedev聊天室中的某人解释该问题时,我发现了我思考的最后一个错误。
如果您还没有忘记原始问题中的图片,我s
在那儿提到,结果s
就是速度(以度为单位),但船只沿路径以像素为单位移动,而我已经忘记了这一事实。因此,在这种情况下,我需要做的是将以度为单位的位移转换为以像素为单位的位移,事实证明这很简单:
function rotationToMovement(planetSize, rotationSpeed) {
var r = shipAngle * Math.PI / 180;
var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
var orbit = planetSize + shipOrbit;
var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
return Math.sqrt(dx * dx + dy * dy);
};
如此而已!谢谢阅读 ;)