角度约束不适用于某些角度

Angle constraint doesn't work for certain angles

我正在为我的 2d 物理引擎编写角度关节。它有效,除非最大角度为负且最小角度为正(当角度直接向左时)。

如您所见,所有其他球都在其公差角度内移动,但最左边的那个球没有。

public class AngleJoint extends Joint {

    private float minAngle;
    private float maxAngle;

    public AngleJoint(
        final GameEntity a, 
        final GameEntity b, 
        final float midAngle, 
        final float tolerance
    ) {
        super(a, b);
        assert tolerance >= 0;
        minAngle = midAngle - tolerance;
        maxAngle = midAngle + tolerance;

        while (minAngle > Math.PI) {
            minAngle -= 2 * Math.PI;
        }
        while (minAngle < -Math.PI) {
            minAngle += 2 * Math.PI;
        }
        while (maxAngle > Math.PI) {
            maxAngle -= 2 * Math.PI;
        }
        while (maxAngle < -Math.PI) {
            maxAngle += 2 * Math.PI;
        }
        System.out.println(minAngle + ", " + maxAngle);
    }

    @Override
    public void update() {
        assert getA() != null && getB() != null;

        final CManifold m = new CManifold();
        m.a = getA();
        m.b = getB();

        final Vec2D aToB = getB().center().minus(getA().center());
        // angle from A to B
        final float angle = aToB.getTheta();

        if (angle >= minAngle && angle <= maxAngle) {
            // we don't need to do anything
            return;
        }

        final float distBtoA = aToB.length();

        final float closestAngleBound 
            = Math.abs(angle - maxAngle) < Math.abs(angle - minAngle) 
            ? maxAngle : minAngle;

        // where we should be
        final Vec2D solvedLocation 
            = getA().center().plus(
                new Vec2D((float) (
                    Math.cos(closestAngleBound) * distBtoA), 
                    (float) (Math.sin(closestAngleBound) * distBtoA)
                )
            );
        final Vec2D correction = solvedLocation.minus(getB().center());
        final float d = correction.length();

        m.setNormal(correction.divide(d));
        m.setPenetration(d);
        Collisions.fixCollision(m, false);
    }

}

这是我创建这个特定场景的地方。

final Vec2D centerV = new Vec2D(500, 700);

    center = createBall(centerV, 75);
    center.setMass(GameEntity.INFINITE_MASS);
    entities.add(center);

    final float vertices = 6;
    final float dist = 120;

    GameEntity first = null;
    GameEntity last = null;
    for (int i = 0; i < vertices; i++) {
        final float angle = (float) (2 * Math.PI / vertices * i);
        final Vec2D newCenter 
            = new Vec2D(
                (float) (centerV.x + Math.cos(angle) * dist), 
                (float) (centerV.y + Math.sin(angle) * dist)
            );
        final GameEntity vertex = createBall(newCenter, 10);
        entities.add(vertex);
        if (last != null) {
            // constraints.add(new DistanceJoint(last, vertex));
        } else {
            first = vertex;
        }
        constraints.add(new DistanceJoint(center, vertex));
        constraints.add(new AngleJoint(center, vertex, angle, .1f));
        last = vertex;
        if (i == vertices - 1 && first != null) {
            // constraints.add(new DistanceJoint(first, vertex));
        }
    }
}

如何修正我的更新方法,使左边的球与其他球的行为相似?

这部分代码:

    if (angle >= minAngle && angle <= maxAngle) {
        // we don't need to do anything
        return;
    }

永远不会导致相关角度的 return;由于 maxAngle 为负且 minAngle 为正,因此角度永远不会既大于正 minAngle 又小于负 maxAngle。

因此,即使角度介于两个限制之间,此关节的执行也会落到看起来角度被迫朝向最近限制的部分。由于它被强制到最接近的限制,因此不能在限制之间自由弹跳。

对于 maxAngle 为负且 minAngle 为正的情况,您需要一些特殊情况代码来替换上面引用的测试。您可能还需要修复其他问题,但这将是一个开始。

我通过首先实施 建议的检查解决了这个问题,但也改变了我的数学。这是完成的关节class

public class AngleJoint extends Joint {

    // angles stored between -Pi and Pi
    private final float minAngle;
    private final float maxAngle;

    /**
     *
     * @param a
     * @param b
     * @param midAngle
     *            the angle in the range of -Pi to Pi.
     * @param tolerance
     *            the angle tolerance in both directions. 0 <= tolerance < Pi
     */
    public AngleJoint(final GameEntity a, final GameEntity b, final float midAngle, final float tolerance) {
        super(a, b);
        if (tolerance < 0 || tolerance >= AngleUtils.PI) {
            throw new IllegalArgumentException("Tolerance must be >= 0 and < Pi");
        }
        minAngle = AngleUtils.normalize(midAngle - tolerance);
        maxAngle = AngleUtils.normalize(midAngle + tolerance);
    }

    @Override
    public void update() {
        assert getA() != null && getB() != null;

        final CManifold m = new CManifold();
        m.a = getA();
        m.b = getB();

        final Vec2D aToB = getB().center().minus(getA().center());
        // angle from A to B
        final float angle = aToB.getTheta();

        if (angle >= minAngle && angle <= maxAngle) {
            // we don't need to do anything
            return;
        }
        // if we are in that dumb spot where maxAngle < min Angle (directly to the left) we need extra checks
        if (maxAngle < minAngle && (angle <= maxAngle && angle >= -AngleUtils.PI || angle >= minAngle && angle <= AngleUtils.PI)) {
            return;
        }

        final float distBtoA = aToB.length();

        final float closestAngleBound = AngleUtils.angleDifference(angle, maxAngle) < AngleUtils.angleDifference(angle, minAngle) ? maxAngle
                : minAngle;

        // where we should be
        final Vec2D solvedLocation = getA().center().plus(
                new Vec2D((float) (Math.cos(closestAngleBound) * distBtoA), (float) (Math.sin(closestAngleBound) * distBtoA)));
        final Vec2D correction = solvedLocation.minus(getB().center());
        final float d = correction.length();

        m.setNormal(correction.divide(d));
        m.setPenetration(d);
        Collisions.fixCollision(m, false);
    }

}