0

我正在编写一个 2d 物理引擎来取乐。

我为不同的形状编写了不同的类,BoxObject是矩形,BallObject是圆形和LineObject直线。所有这些都实现了PhysicsObject接口。

正在模拟的所有对象都位于一个列表中。

public static List<PhysicsObject> objects = new ArrayList<PhysicsObject>();

所有这些对象都需要检查是否相互碰撞。

Collision类包含命名penetrationData为获取所有可能类型对的穿透数据的方法,例如penetrationData(BoxObject a, BallObject b)penetrationData(BallObject a, LineObject b)。这就是它的样子。

package com.optimus.game.physics;

public class Collision {

    // Default - no collision
    public static float[] penetrationData(PhysicsObject a, PhysicsObject b) {
        return null;
    }

    // Line vs Circle
    public static float[] penetrationData(LineObject line, BallObject ball) {
        float distance = line.distance(ball.x, ball.y);
        if (Math.abs(distance) > ball.radius) {
            return null;
        }
        float penetration = ball.radius - Math.abs(distance);
        float normalX = line.perpendicularX();
        float normalY = line.perpendicularY();
        return new float[] {normalX, normalY, penetration};
    }

    // Circle vs Line
    public static float[] penetrationData(BallObject ball, LineObject line) {
        float[] data = penetrationData(line, ball);
        // reverse the normal
        if (data != null) {
            data[0] = -data[0];
            data[1] = -data[1];
        }
        return data;
    }

    // Circle vs Circle
    public static float[] penetrationData(BallObject a, BallObject b) {

        // Vector from A to B
        float normalX = b.x - a.x;
        float normalY = b.y - a.y;
        float penetration = 0;

        // calculate the penetration and normal direction
        // ...
        // ... (code skipped)

        return new float[] {normalX, normalY, penetration};
    }

    // Rect vs Rect
    public static float[] penetrationData(BoxObject a, BoxObject b) {
        // Vector from A to B
        float normalX = b.x - a.x;
        float normalY = b.y - a.y;
        float penetration = 0;

        // calculate the penetration and normal direction
        // ...
        // ... (code skipped)

        return new float[] {normalX, normalY, penetration};
    }

    // Rect vs Circle
    public static float[] penetrationData(BoxObject box, BallObject ball) {
        // Vector from A to B
        float normalX = ball.x - (box.x + box.width / 2f);
        float normalY = ball.y - (box.y + box.height / 2f);
        float penetration = 0;

        // calculate the penetration and normal direction
        // ...
        // ... (code skipped)

        return new float[] {normalX, normalY, penetration};
    }

    // Circle vs Rect
    public static float[] penetrationData(BallObject ball, BoxObject box) {
        float[] data = penetrationData(box, ball);
        // reverse the normal
        if (data != null) {
            data[0] = -data[0];
            data[1] = -data[1];
        }
        return data;
    }


}

我检查碰撞像......

public static void checkAndResolveCollisions(List<PhysicsObject> objects) {
    // check collision between all objects in a list
    // list of objects can contain BallObject, LineObject and
    // BoxObject all of which implement PhysicsObject

    // loop over all possible pairs
    for (int i = 0; i < objects.size(); i++) {
        for (int j = i + 1; j < objects.size(); j++) {

            // here I want that apt function is called based on type
            // but objects.get(int i) always returns a PhysicsObject
            float[] data = Collision.penetrationData(objects.get(i), objects.get(j));

            // data = null implies no overlap
            if (data == null) {
                continue;
            }

            // Calculate relative velocity in terms of the normal direction
            float normalX = data[0];
            float normalY = data[1];
            float penetration = data[2];

            // Resolve the collision along the normal
            // ...
            // ... (code skipped)

        }
    }
}

但它不起作用并且每次都penetrationData返回......null

如果我得到这样的渗透数据,但是这个代码很臭,我可以通过查看它来判断......

// here is what works, but I know is bad java code
float[] data = getAptPenetrationData(objects.get(i), objects.get(j));

getAptPenetrationData类型转换然后调用 apt 函数的通用函数在哪里

// generic function that calls others
    public static float[] getAptPenetrationData(PhysicsObject a, PhysicsObject b) {
        // bad java code...
        boolean aIsBall = a instanceof BallObject;
        boolean aIsBox = a instanceof BoxObject;
        boolean aIsLine = a instanceof LineObject;

        boolean bIsBall = b instanceof BallObject;
        boolean bIsBox = b instanceof BoxObject;
        boolean bIsLine = b instanceof LineObject;

        float[] data = null;
        // circle vs circle
        if (aIsBall && bIsBall) {
            data = Collision.penetrationData((BallObject)a, (BallObject)b);
        // box vs box
        } else if (aIsBox && bIsBox) {
            data = Collision.penetrationData((BoxObject)a, (BoxObject)b);
        // box vs circle
        } else if (aIsBox && bIsBall) {
            data = Collision.penetrationData((BoxObject)a, (BallObject)b);
        // circle vs box
        } else if (aIsBall && bIsBox) {
            data = Collision.penetrationData((BallObject)a, (BoxObject)b);
        // circle vs line
        } else if (aIsBall && bIsLine) {
            data = Collision.penetrationData((BallObject)a, (LineObject)b);
        // line vs circle
        } else if (aIsLine && bIsBall) {
            data = Collision.penetrationData((LineObject)a, (BallObject)b);
        }
        return data;
    }

我一定错过了一些东西,并且必须有更好的方法来做我想做的事情。任何帮助表示赞赏。谢谢。

编辑:

根据 Zim-Zam 的建议,碰撞现在是这样解决的……

public static void checkAndResolveCollisions(List<PhysicsObject> objects) {
    // check collision between all objects in a list
    // list of objects can contain BallObject, LineObject and
    // BoxObject all of which implement PhysicsObject

    // loop over all possible pairs
    for (int i = 0; i < objects.size(); i++) {
        for (int j = i + 1; j < objects.size(); j++) {

            // does not work, goes into infinite recursion
            // still calls PhysicsObject.penetrationData(PhysicsObject)
            float[] data = objects.get(i).penetrationData(objects.get(j));

            // data = null implies no overlap
            if (data == null) {
                continue;
            }

            // Calculate relative velocity in terms of the normal direction
            float normalX = data[0];
            float normalY = data[1];
            float penetration = data[2];

            // Resolve the collision along the normal
            // ...
            // ... (code skipped)

        }
    }
}

但它只有在我将以下方法签名添加到PhysicsObject接口时才有效

public PenetrationData penetrationData(PhysicsObject other);
public PenetrationData penetrationData(BallObject other);
public PenetrationData penetrationData(BoxObject other);
public PenetrationData penetrationData(LineObject other);

这仍然不是理想的,因为每次我实现一个新的PhysicsObject我都必须更改接口。

PhysicsObject 实现(仅限penetrationData函数) -

BallObject.java

public class BallObject implements PhysicsObject {

    @Override
    public float[] penetrationData(PhysicsObject other) {
        return other.penetrationData(this);
    }
    @Override
    public float[] penetrationData(LineObject line) {
        return Collision.penetrationData(this, line);
    }
    @Override
    public float[] penetrationData(BoxObject box) {
        return Collision.penetrationData(this, box);
    }
    @Override
    public float[] penetrationData(BallObject ball) {
        return Collision.penetrationData(this, ball);
    }

    // ... rest of the code
}

BoxObject.java

public class BoxObject implements PhysicsObject {

    @Override
    public float[] penetrationData(PhysicsObject other) {
        return other.penetrationData(this);
    }
    @Override
    public float[] penetrationData(LineObject line) {
        return null; // not implemented
    }
    @Override
    public float[] penetrationData(BoxObject box) {
        return Collision.penetrationData(this, box);
    }
    @Override
    public float[] penetrationData(BallObject ball) {
        return Collision.penetrationData(this, ball);
    }

    // ... rest of the code
}

LineObject.java

public class LineObject implements PhysicsObject {

    @Override
    public float[] penetrationData(PhysicsObject other) {
        return other.penetrationData(this);
    }
    @Override
    public float[] penetrationData(LineObject line) {
        return null; // not implemented
    }
    @Override
    public float[] penetrationData(BoxObject box) {
        return null; // not implemented
    }
    @Override
    public float[] penetrationData(BallObject ball) {
        return Collision.penetrationData(this, ball);
    }

    // ... rest of the code
}
4

3 回答 3

3

如前所述,您需要双重调度。但是,我不建议使用重量级和样板式的访问者模式。您应该用 Java 实现自己的调度系统。例如,构建从对象对到渗透数据计算器的映射。然后instanceof,您将拥有很多 s 而不是很多 s map.put()。您甚至可以put通过使用varargs方法来避免重复,该方法可用于伪造地图文字

static Map<ObjPair, CollisionCalc> map(Object... kvs) {
   final Map<ObjPair, CollisionCalc> ret = new HashMap<>();
   for (int i = 0;;i < kvs.length) 
     ret.put((ObjPair)kvs[i++], (CollisionCalc)kvs[i++]);
   return ret;
}

在客户端,这应该如下所示:

public static float[] penetrationData(PhyObject o1, PhyObject o2) {
  return map.get(new ObjPair(o1, o2)).penetrationData(o1, o2);
}
于 2013-06-23T15:09:14.683 回答
2

一种选择是使用多方法扩展,例如Java MultiMethod Framework 它在运行时自动执行参数向下转换。例如,将自动解析为或其他。Collision.penetrationData(physicaObject obj1, physicsObject obj2)Collision.penetrationData(ballObject obj1, squareObject obj2)

physicsObject1.resolvePenetrationData(physicsObject obj1)另一种选择是在每个子类中放置一个方法physicsObject——这将自动确定physicsObject1. 然后在resolvePenetrationData你可以确定physicsObject obj1参数的实际类型并调用相应的静态方法。您仍将手动确定其中一个对象的类型,但会自动解析另一个对象的类型。或者您可以通过重载resolvePenetrationData来自动解析这两种对象类型以接受一个PhysicsObject参数和一个BallObject参数等。调用resolvePenetrationData两次,每个参数一次,

class BallObject {
    float[] resolvePenetrationData(PhysicsObject obj1) {
        return obj1.resolvePenetrationData(this);
    }
    float[] void resolvePenetrationData(BallObject obj1) {
        return Collision.penetrationData(this, obj1);
    }
    float[] resolvePenetrationData(SquareObject obj1) {
        return Collision.penetrationData(this, obj1);
    }
    //etc
}
于 2013-06-23T15:07:59.683 回答
1

不幸的是,真的没有。Java 的虚拟方法提供单次分派,但您需要的是双分派。

使用巨型开关/if else 的明显方法的一种替代方法是执行类似访问者的模式。但是,根据您的需要,这实际上可能会更好,也可能不会更好。

于 2013-06-23T15:04:44.097 回答