воскресенье, 26 августа 2012 г.

Коллизии

В процессе нашего изучения Box2d мы не затронули очень важную тему - обработку событий. Разумеется, Box2d обрабатывает события столкновения объектов вполне самостоятельно и без нашей помощи, но иногда нам тоже хочется принять в этом участие :) Внесем изменения в наш проект Kepler таким образом, чтобы столкнувшиеся объекты "сливались" в один.
Один из столкнувшихся объектов будем удалять, а у второго будем изменять параметры. Нам необходимо зарегистрировать объект ContactListener, чтобы получать сообщения  о коллизиях и внести в проект изменения, связанные с обработкой этих коллизий. Главное, о чем необходимо помнить, это то, что объекты Box2d НЕ ДОЛЖНЫ изменяться, в процессе расчета очередной итерации. Получив уведомление о коллизии, мы должны сохранить параметры, для того, чтобы выполнить необходимые изменения после расчета итерации Box2d. Измененный модуль Task будет выглядеть следующим образом:

package com.WhiteRabbit.Kepler;

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.Contact;

public class Task extends TimerTask implements ContactListener {

    private static long SECONDS_INTERVAL = 1000L;
    private static long FRAME_RATE = 15L;
   
    private KeplerActivity ctx;
    private Timer timer = new Timer();
   
    private World             world;
    private Body              ground;
   
    public Task(KeplerActivity ctx) {
        this.ctx  = ctx;
        configure();
    }
   
    @Override
    public void beginContact(Contact contact) {}

    @Override
    public void endContact(Contact contact) {}

    @Override
    public void preSolve(Contact contact, Manifold folds) {}
   
    @Override
    public void postSolve(Contact contact, ContactImpulse impulse) {
        Item a = (Item)contact.getFixtureA().getBody().getUserData();
        Item b = (Item)contact.getFixtureB().getBody().getUserData();
        if (a.isMarked() || b.isMarked()) return;
        a.mark(); b.delete();
        Vec2 aSpeed = a.getBody().getLinearVelocity();
        Vec2 bSpeed = b.getBody().getLinearVelocity();
        a.getBody().setLinearVelocity(new Vec2(aSpeed.x + bSpeed.x, 

                                               aSpeed.y + bSpeed.y));
        a.setRadius((float)Math.sqrt((a.getRadius()*a.getRadius()) +  

                                     (b.getRadius()*b.getRadius())));
    }

    private void configure() {
        MainView v = ctx.getView();
        float X = v.SZ_X / 2;
        float Y = v.SZ_Y / 2;
       
        // Создать мир
        Vec2 gravity = new Vec2(0.0f, 0.0f);
        world = new World(gravity, true);
        world.setContactListener(this);
        BodyDef bd = new BodyDef();
        ground = world.createBody(bd);
       
        // Создать границы
        bd.position.set(0, Y);
        Body edge = world.createBody(bd);
        PolygonShape sd = new PolygonShape();
        sd.setAsBox(1, Y);
        edge.createFixture(sd, 1);
        bd.position.set(2 * X, Y);
        edge = world.createBody(bd);
        edge.createFixture(sd, 1);
        bd.position.set(X, 0);
        edge = world.createBody(bd);
        sd.setAsBox(X, 1);
        edge.createFixture(sd, 1);
        bd.position.set(X, 2 * Y);
        edge = world.createBody(bd);
        edge.createFixture(sd, 1);
       
        // Создать объекты
        CircleShape shape = new CircleShape();
        bd.type = BodyType.DYNAMIC;
        Random r = new Random();
       
        for (int i = 0; i < 15; i++) {
            bd.position.set(r.nextInt((int)X * 2 - 10) + 5, 

                            r.nextInt((int)Y * 2 - 10) + 5);
            bd.linearVelocity.set(r.nextInt(20) - 5, r.nextInt(20) - 10);
            shape.m_radius = r.nextInt(4) + 1;
            Body b = world.createBody(bd);
            b.createFixture(shape, r.nextInt(3) + 1);
            ctx.getView().addBody(b, shape.m_radius);
        }
       
        // Запустить рассчет
        timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
    }
   
    public void terminate() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    @Override
    public void run() {
        // Выполнить расчет итерации
        float timeStep = 2.0f / FRAME_RATE;
        world.step(timeStep, 10, 10);
        // Удалить столкнувшиеся объекты
        ctx.getView().removeDeleted(world);
        // Рассчитать действующие силы
        ctx.getView().calcForces();
        // Обновить view
        ctx.getView().update();
    }
}


  1. Мы наследуем интерфейс ContactListener и регистрируем его вызовом setContactListener
  2. Для обработки коллизий будем использовать метод postSolve (от других он выгодно отличается наличием параметра impulse, по которому можно оценивать "силу" соударения, чего мы правда не будем делать)
  3. В configure вместо 3 генерируем 15 объектов, используя генератор случайных чисел для формирования начальных параметров
  4. Добавляем границы по краям экрана, чтобы объекты не улетали в открытое пространство
  5. В методе run добавляем фазу удаления объектов помеченных для удаления
В MainView немного изменяем метод addBody (сохраняя в Body ссылку на объект Item) и добавляем метод removeDeleted, осуществляющий удаление объектов:

package com.WhiteRabbit.Kepler;

import java.util.ArrayList;
import java.util.List;

import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.World;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainView extends SurfaceView implements SurfaceHolder.Callback {
    
    ...
    public void addBody(Body b, float r) {
        Item it = new Item(b, r);
        b.setUserData(it);
        items.add(it);
    }
   
    public void removeDeleted(World w) {
        List<Item> newItems = new ArrayList<Item>();
        for (Item it: items) {
            if (it.isDeleted()) {
                w.destroyBody(it.getBody());
            } else {
                if (it.isMarked()) {
                    Body b = it.getBody();
                    CircleShape shape = new CircleShape();
                    shape.m_radius = it.getRadius();
                    Fixture f = b.getFixtureList();
                    if (f !=null) {
                        b.destroyFixture(f);
                    }
                    b.createFixture(shape, 1);
                    it.clear();
                }
                newItems.add(it);
            }
        }
        items = newItems;
    }
    ...   
}

Item приобретает новые члены, но не становится от этого менее тривиальным:

package com.WhiteRabbit.Kepler;

import org.jbox2d.dynamics.Body;

public class Item {
   
    private Body body;
    private float radius;
    private boolean isDeleted = false;
    private boolean isMarked = false;
   
    public Item(Body body, float radius) {
        this.body = body;
        this.radius = radius;
    }
   
    public boolean isDeleted() {
        return isDeleted;
    }
   
    public boolean isMarked() {
        return isDeleted || isMarked;
    }
   
    public void delete() {
        isDeleted = true;
    }
   
    public void mark() {
        isMarked = true;
    }
   
    public void clear() {
        isMarked = false;
    }
   
    public Body getBody() {
        return body;
    }
   
    public float getRadius() {
        return radius;
    }
   
    public void setRadius(float radius) {
        this.radius = radius;
    }
}
Вот и все. Запускаем программу на выполнение и наблюдаем картину "должен остаться только один":



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

Комментариев нет:

Отправить комментарий