2013-06-23 3 views
0

Я пишу 2-й физический движок для развлечения.Вызовите подходящую функцию, основанную на типе объектов

Я написал разные классы для разных форм. 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 является общей функцией, приведения типов, а затем вызывает функцию склонный

// 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; 
    } 

Я должен что-то отсутствует, и там должен быть лучший способ сделать то, что я пытаюсь сделать. Любая помощь приветствуется. Благодарю.

Edit:

Согласно предложению ЗИМ-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 
} 

Line Объект.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 
} 
+0

Было бы лучше иметь общий интерфейс/абстрактный класс для ваших объектов и использовать общие методы из интерфейса/абстрактного класса вместо выполнения всех этих сравнений 'if-else'. –

+1

От Effective C++, Scott Meyers: * «В любое время, когда вы находите себе код формы», если объект имеет тип T1, а затем что-то делать, но если он имеет тип T2, тогда сделайте что-нибудь еще, «* slap –

+1

@Baadshah Я бы ... но это не улучшило бы мой код ... ваши предложения могут ... – Optimus

ответ

3

Как уже было отмечено, что вам нужно двойную диспетчеризацию. Тем не менее, я бы не посоветовал супертяжелый и шаблонный шаблон Visitor. Вы должны внедрить свою собственную диспетчерскую систему на Java. Например, постройте карту из пары объектов в калькулятор данных проникновения. Тогда вместо большого количества instanceof s у вас будет много map.put(). Вы даже можете избежать повторений put путем использования переменной длиной метода, который может быть использован для фальшивого карты буквального:

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); 
} 
+0

спасибо, это выглядит чище ... Я попробую. .. – Optimus

+0

Я пробовал шаблон посетителя и нашел его непрактичным, потому что мне нужно добавить дополнительные реализации PhysicsObject в будущем, а затем я буду вынужден изменить свой интерфейс. Я, наконец, принял такой подход ... спасибо ... – Optimus

1

К сожалению, на самом деле нет. Виртуальные методы Java обеспечивают единую отправку, но вам нужна двойная отправка.

Одна из альтернатив очевидному подходу к использованию гигантского переключателя/если еще нужно сделать шаблон, похожий на посетителя. Однако это может быть или не быть лучше в зависимости от ваших потребностей.

+0

@Hovercraft Я на самом деле просто добавил это. – Antimony

+0

Спасибо .. I «Посмотрим на« Шаблон посетителя »... – Optimus

2

Один из вариантов заключается в использовании расширения multimethod, например. Java MultiMethod Framework, который автоматически выполняет функции downcasts во время выполнения. Например, 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 
} 
+0

ну, один из типов еще нужно решить ... потому что я итерации по списку , который возвращает PhysicsObject ... так что 'ball.resolvePenetrationData (square)' все равно вызовет 'BallObject.resolvePenetrationData (PhysicsObject)' ... – Optimus

+0

@Optimus Метод 'resolvePenetrationData (PhysicsObject obj1)' вызывает 'resolvePenetrationObject' на' obj1', который автоматически разрешает его тип. –

+0

правильно ... это умно ... спасибо, я попробую ... кажется, самый простой способ ... – Optimus

Смежные вопросы