【问题标题】:Javascript physics engine and simulated infinite curveJavascript物理引擎和模拟无限曲线
【发布时间】:2026-02-12 23:15:01
【问题描述】:

我正在尝试像在 javascript 中那样做一个 Tiny Wings。

我第一次看到a technique using Box2D,我正在使用closure-web version(因为内存泄漏修复)。
简而言之,我将曲线分解为多边形,如下所示:

我也尝试过使用 Chipmunk-js,并使用分段形状来模拟我的地面:

在这两种情况下,当一个圆圈滚动时,我在多边形或线段之间的公共点处都遇到了一些“崩溃”或“颠簸”。

我向 Chipmunk 询问了这个问题,作者说他为该段实现了一个半径属性以减少这种行为。我试过了,它确实成功了,但它并不完美。我仍然有一些颠簸(我必须将半径设置为 30 像素才能获得积极的效果)。

“凸起”附加在两个多边形之间的共享点上:

使用,正如illandril 向我建议的那样,边缘技术(他只测试了多边形-多边形接触)来避免圆在边缘上崩溃:

还尝试按照 Luc 的建议添加 项目符号选项,但似乎没有任何改变。

这里是the demo 的问题。
您可以尝试更改要检查的值:

  • 子弹选项
  • 边缘尺寸
  • 迭代次数
  • 物理学

(仅在最新的开发版 Chrome 上测试)
耐心点(或改变水平重力),你就会明白我的意思了。
这里the repo有兴趣。

【问题讨论】:

  • 你需要的是一个三次样条,它可以保证顶点和斜率的连续性。
  • 我实际上正在使用Smooth.js 和三次方法来弯曲我的地面。这不是问题,因为您可以尝试使用具有多个相邻多边形(或线段)的线性坡度,当圆在边缘滚动时,您仍然会得到“凸起”。
  • Smooth.js 将三次分别应用于每个维度,并且不准确处理斜率。查看getTangent 方法,它执行有限差分(next_value-prev_value)/stride,而不是正确的数学处理。我不知道js,但在FORTRANC# 中我使用了数值配方(apps.nrbook.com/c/index.html)第3.3 节,三次样条。
  • 让我补充一点,三次样条不仅在值和斜率上建立连续性,而且在曲率上也建立连续性。
  • 恐怕这超出了我的能力范围:/ 你能用一个方案解释一下吗?我正在使用 Smooth.js 来弯曲我的分组斜坡,它似乎在视觉和物理上都有效。我猜问题是两个相邻的多边形共享一些点,请参阅更新问题中的this drawing。我正在设置一个演示,以便能够更明确。

标签: javascript box2d physics curve chipmunk


【解决方案1】:

最好的解决方案是带有幻影顶点的边缘形状,但如果您使用的版本/端口中不可用,那么下一个最好的方法就是您的问题中称为“边缘”的图表,但将多边形进一步扩展到地下坡度很浅,就像在这个线程中一样:http://www.box2d.org/forum/viewtopic.php?f=8&t=7917

【讨论】:

  • 帖子中的有趣技术,我会尝试并寻找幽灵顶点。
【解决方案2】:

我最初认为问题可能来自两个相邻线段之间的坡度变化,但由于在多边形的平坦表面上仍然有凹凸,我认为问题在于击中多边形的角。

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

![图表][1]

这应该适用于凹曲线,并且......无论如何你应该飞越凸曲线。

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

-- 编辑

在 Box2D.js 的第 5082 行(至少在 this repo 中)你有 PreSolve(contact,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 
};

【讨论】:

  • 感谢您的想法,但我无法实现它。它还在颠簸。
  • 我仍然认为你的盒子的角落是问题所在。请参阅iforce2d.net/b2dtut/collision-anatomy - 显然,您希望您的雪球以垂直于其表面的脉冲弹跳一段。撞到角落时,情况并非如此。尝试将雪球设置为子弹体(参见bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=5073#p18530)。
  • @SuperSkunk,你能看看碰撞时得到的流形,看看它们是否远离曲线表面的法线吗?我已经更新了我的答案,以使我的观点更具可读性。
  • 我要慢慢来了。仍然需要弄清楚如何从 PreSolve 中计算曲线的点(参见 TODO 注释块)
  • 我现在不能处理它,但我明白你的意思,我会在周末尝试一下!你已经得到了一些积极的结果吗?
【解决方案3】:

我想说这个问题已经在 Box2D 2.2.0 中得到解决,请参阅 its manual, section 4.5 "Edge Shapes"

这个东西是it's a feature of the 2.2.0 version,还有chainshape这个东西,box2dweb实际上是从2.2.1a移植过来的——不知道box2dweb-closure。

我通过修改 Box2D.Collision.b2Collision.CollidePolygonAndCircle 进行的任何尝试都导致了不稳定的行为。至少有一部分时间(例如,球在随机方向上碰撞,但仅在它缓慢滚动时)。

【讨论】: