JavaScript 物理引擎和模拟无限曲线

21

我想用JavaScript实现类似Tiny Wings的游戏。

我最初看到了使用Box2D的一种技术,我正在使用closure-web版本(因为它修复了内存泄漏问题)。
简而言之,我将曲线分解成多边形,使其看起来像这样:

enter image description here

我也尝试使用Chipmunk-js,并使用线段形状来模拟我的地面,就像这样:

enter image description here

在这两种情况下,当圆滚动时,在多边形或线段的公共点之间我会遇到一些“崩溃”或“颠簸”的情况。我向Chipmunk询问了此事,作者说他为线段实现了一个半径属性以减少这种行为。我尝试了一下,确实有用,但并不完美。我仍然会遇到一些颠簸(我必须将半径设置为30像素才能获得积极效果)。这些“颠簸”会发生在两个多边形之间的共享点上:

bump bug

illandril所建议的,使用边缘技术(他只测试了多边形与多边形接触)来避免圆形撞上边缘:

slope edging technique

Luc建议我尝试添加bullet选项,但似乎没有任何变化。

这里是问题的演示
您可以尝试更改以下值以进行检查:

  • bullet选项
  • 边缘大小
  • 迭代次数
  • 物理属性

(仅在最新的开发版Chrome上测试过)
请耐心等待(或更改水平重力),您会明白我的意思。
这里是感兴趣者的代码库


1
你需要的是一个三次样条函数,它保证了顶点和斜率的连续性。 - John Alexiou
我实际上正在使用Smooth.js和三次样条曲线方法来使我的地面弯曲。这不是问题,因为您可以尝试使用多个相邻的多边形(或线段)的线性斜率,当圆在边缘滚动时仍然会出现“颠簸”。 - SuperSkunk
Smooth.js 对每个维度分别应用三次函数,但不精确处理斜率。请查看 getTangent 方法,它使用有限差分 (next_value-prev_value)/stride 而非正确的数学处理方法。我不懂 js,但在 FORTRANC# 中,我使用了数值计算方法 (http://apps.nrbook.com/c/index.html) 第 3.3 节,即三次样条插值。 - John Alexiou
让我补充一下,立方样条不仅在值和斜率上建立连续性,而且还在曲率上建立连续性。 - John Alexiou
我恐怕超出了我的能力 :/ 你能用图示解释一下吗? 我正在使用Smooth.js来曲线化我的分组斜坡,它在视觉和物理上似乎都有效。 我猜问题在于两个相邻的多边形共享一些点,可以参考这张图片中更新的问题。 我正在设置一个演示以更明确地说明。 - SuperSkunk
3个回答

2
最佳解决方案是使用带有幽灵顶点的边缘形状,但如果您使用的版本/端口中没有该选项,则下一个最佳选择类似于您问题中称为“edging”的图表,但应该将多边形向地下延伸,并采用非常浅的坡度,就像在此主题中所示:http://www.box2d.org/forum/viewtopic.php?f=8&t=7917

这篇文章中有一个有趣的技巧,我会尝试一下,并且也会寻找一下幽灵顶点。 - SuperSkunk

1

我最初认为问题可能来自于相邻两个线段之间的斜率变化,但由于在多边形的平坦表面上仍然有凸起,我认为问题更可能是撞到了多边形的角落。

我不知道是否可以设置两组重叠的多边形?只需使用相同的插值计算,并生成第二组多边形,就像下图所示:您已经构建了红色的多边形集,并通过将绿色多边形的左顶点设置在红色多边形的中心,并将其右顶点设置在下一个红色多边形的中心来添加绿色多边形集。

![diagram][1]

这应该适用于凹曲线...好吧,你应该已经飞过凸曲线了。

如果这行不通,请尝试设置大量多边形以构建斜坡。将圆的半径的十分之一用作多边形的宽度,甚至可以更小。这应该可以减少您的斜率不连续性。

--编辑

在Box2D.js的第5082行(至少在this repo中),您可以重写PreSolve(contact, manifold)函数以检查雪球与多边形碰撞时的冲量方向是否正确。
为此,您需要恢复manifold向量并将其与曲线的法向量进行比较。它应该看起来像这样(可能不完全相同):
Box2D.Dynamics.b2ContactListener.prototype.PreSolve = function (contact, oldManifold) {
    // contact instanceof Box2D.Dynamics.Contacts.b2Contact == true
    var localManifold, worldManifold, xA, xB, man_vect, curve_vect, normal_vect, angle;
    localManifold = contact.GetManifold();

    if(localManifold.m_pointCount == 0)
        return; // or raise an exception

    worldManifold = new Box2D.Collision.b2WorldManifold();
    contact.GetWorldManifold( worldManifold );

    // deduce the impulse direction from the manifold points
    man_vect = worldManifold.m_normal.Copy();

    // we need two points close to & surrounding the collision to compute the normal vector
    // not sure this is the right order of magnitude
    xA = worldManifold.m_points[0].x - 0.1;
    xB = worldManifold.m_points[0].x + 0.1;

    man_vect.Normalize();

    // now we have the abscissas let's get the ordinate of these points on the curve
    // the subtraction of these two points will give us a vector parallel to the curve

    var SmoothConfig;

    SmoothConfig = {
        params: {
            method: 'cubic',
            clip: 'mirror',
            cubicTension: 0,
            deepValidation: false
        },
        options: {
            averageLineLength: .5
        }
    }
    // get the points, smooth and smooth config stuff here
    smooth = Smooth(global_points,SmoothConfig);

    curve_vect = new Box2D.Common.Math.b2Vec2(xB, smooth(xB)[1]);
    curve_vect.Subtract(new Box2D.Common.Math.b2Vec2(xA, smooth(xA)[1]));

    // now turn it to have a normal vector, turned upwards
    normal_vect = new Box2D.Common.Math.b2Vec2(-curve_vect.y, curve_vect.x);
    if(normal_vect.y > 0)
        normal_vect.NegativeSelf();
    normal_vect.Normalize();
    worldManifold.m_normal = normal_vect.Copy();

    // and finally compute the angle between the two vectors
    angle = Box2D.Common.Math.b2Math.Dot(man_vect, normal_vect);

    $('#angle').text("" + Math.round(Math.acos(angle)*36000/Math.PI)/100 + "°");
    // here try to  raise an exception if the angle is too big (maybe after a few ms)
    // with different thresholds on the angle value to see if the bumps correspond
    // to a manifold that's not normal enough to your curve 
};

谢谢你的想法,但我无法实现它。它仍然出现了问题。 - SuperSkunk
1
我仍然认为你的盒子的角是问题所在。请参阅http://www.iforce2d.net/b2dtut/collision-anatomy-显然,您希望您的雪球以垂直于其表面的脉冲弹跳。撞到一个角落时,情况并非如此。尝试将雪球设置为子弹体(请参见http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=5073#p18530)。 - Cimbali
@SuperSkunk,你能看一下碰撞时得到的多面体,看看它们是否远离曲线表面的法线吗?我已经更新了我的答案,以使我的观点更易读。 - Cimbali
1
我正在慢慢地接近目标。仍需要解决如何在PreSolve中计算曲线点的问题(请参见TODO注释块)。 - Cimbali
我现在不能处理它,但我知道你的意思,我会尝试在周末试一试!你已经得到了一些积极的结果吗? - SuperSkunk
@SuperSkunk 更新了上面的代码,看起来曲线平坦部分的向量是相同的,除了雪球弹跳时。我不知道是否有一种方法可以修改或设置这些向量。我认为你应该覆盖另一个求解器方法,计算雪球将要采取的方向。我可能稍后会研究一下。 - Cimbali

0

我认为这个问题已经在Box2D 2.2.0中得到解决,详见其手册第4.5节“边缘形状”

问题在于这是2.2.0版本的一个功能,连同chainshape一起,而box2dweb实际上是从2.2.1a移植过来的——不知道box2dweb-closure怎么样。

我尝试通过修改Box2D.Collision.b2Collision.CollidePolygonAndCircle来解决问题,但结果表现不稳定。至少有一部分时间(例如球以缓慢的速度滚动时),会出现球向随机方向弹跳的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接