Package org.newdawn.fizzy

Source Code of org.newdawn.fizzy.World$OutOfBoundsCallback

package org.newdawn.fizzy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.callbacks.QueryCallback;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.Manifold;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.contacts.Contact;

/**
* The central object of the simulation. The world contains the bodies (and
* joints) which model the world and react to the physics engine.
*
* @author kevin
*/
public class World {
  public enum OutOfBoundsBehavior {
    /**
     * Do nothing to objects that go out of bounds.
     */
    NONE,
    /**
     * Deactivate objects that go out of bounds (mark them as not active)
     */
    DEACTIVATE,
    /**
     * Remove objects from the world completely that go out of bounds.
     */
    DESTROY
  }

  /** The default gravity applied if none is specified (-10) */
  public static final float DEFAULT_GRAVITY = -10f;
  /**
   * The default number of iteration used in the integration if none specified
   * (10)
   */
  public static final int DEFAULT_ITERATIONS = 10;

  /** The JBox2D world this World object is wrapping */
  private org.jbox2d.dynamics.World jboxWorld;
  /** The list of bodies added to the world */
  private List<Body<?>> bodies = new ArrayList<Body<?>>();
  /**
   * A map from shapes that will be reported from collision to the bodies that
   * own them
   */
  private Map<org.jbox2d.collision.shapes.Shape, Body<?>> shapeMap = new HashMap<org.jbox2d.collision.shapes.Shape, Body<?>>();
  /** The list of listeners to be notified of collision events */
  private List<WorldListener> listeners = new ArrayList<WorldListener>();

  /** List of listeners that are associated with particular bodies */
  private Map<Body<?>, Set<WorldListener>> bodyListeners = new HashMap<Body<?>, Set<WorldListener>>();

  private AABB worldAABB;
  private AABB[] outOfBoundsRegions;
  private OutOfBoundsCallback outOfBoundsCallback;
  private OutOfBoundsBehavior outOfBoundsBehavior = OutOfBoundsBehavior.DEACTIVATE;

  /** The number of iterations to integrate over */
  private int velocityIterations;
  private int positionIterations;

  public World() {
    this(DEFAULT_GRAVITY);
  }

  public World(float gravity) {
    this(new Vec2(0f, gravity));
  }

  public World(Vec2 gravity) {
    setIterations(DEFAULT_ITERATIONS);
    boolean doSleep = true;
    jboxWorld = new org.jbox2d.dynamics.World(gravity, doSleep);
    jboxWorld.setContactListener(new ProxyContactListener());
  }

  /**
   * Get the JBox2D world that is being wrapped
   *
   * @return The JBox2D world that is being wrapped
   */
  org.jbox2d.dynamics.World getJBoxWorld() {
    return jboxWorld;
  }

  /**
   * Add a body to the world
   *
   * @param body
   *            The body to be added to the world
   */
  public void add(Body<?> body) {
    body.addToWorld(this);
    List<org.jbox2d.collision.shapes.Shape> shapes = body.getShape()
        .getJBoxShapes();

    for (int i = 0; i < shapes.size(); i++) {
      shapeMap.put(shapes.get(i), body);
    }
    bodies.add(body);
  }

  /**
   * Remove a body from the world
   *
   * @param body
   *            The body to be removed from the world
   */
  public void remove(Body<?> body) {
    List<org.jbox2d.collision.shapes.Shape> shapes = body.getShape()
        .getJBoxShapes();

    for (int i = 0; i < shapes.size(); i++) {
      shapeMap.remove(shapes.get(i));
    }
    body.removeFromWorld(this);
    bodies.remove(body);
    bodyListeners.remove(body);
  }

  /**
   * Get the number of bodies in the world
   *
   * @return The number of bodies in the world
   */
  public int getBodyCount() {
    return bodies.size();
  }

  /**
   * Get a body at a particular index in the list of bodies
   *
   * @param index
   *            The index of the body to retrieve
   * @return The body at the given index
   */
  public Body<?> getBody(int index) {
    return bodies.get(index);
  }

  /**
   * Update the world
   *
   * @param timeStep
   *            The amount of time to simulate
   */
  public void update(float timeStep) {
    jboxWorld.step(timeStep, velocityIterations, positionIterations);
    resolveOutOfBounds();
  }

  private class BodyQueryCallbackHelper implements QueryCallback {
    private Collection<Body<?>> bodies;

    public BodyQueryCallbackHelper(Collection<Body<?>> bodies) {
      this.bodies = bodies;
    }

    @Override
    public boolean reportFixture(Fixture fixture) {
      Body<?> body = shapeMap.get(fixture.getShape());
      if (body != null) {
        bodies.add(body);
      }
      return true;
    }
  };

  private void resolveOutOfBounds() {
    /*
     * Only continue if: a) out of bounds is defined, and b) either a
     * callback or a non-NONE out of bounds behavior is set
     */
    if (outOfBoundsRegions == null
        || (outOfBoundsBehavior != OutOfBoundsBehavior.NONE || outOfBoundsCallback != null))
      return;
    Collection<Body<?>> bodies = new LinkedList<Body<?>>();
    QueryCallback queryCallback = new BodyQueryCallbackHelper(bodies);
    for (AABB region : outOfBoundsRegions) {
      jboxWorld.queryAABB(queryCallback, region);
    }

    if (outOfBoundsCallback != null) {
      boolean continueProcessing = outOfBoundsCallback
          .reportBodies(bodies);
      if (!continueProcessing) {
        return;
      }
    }
    if (outOfBoundsBehavior != OutOfBoundsBehavior.NONE) {
      for (Body<?> body : bodies) {
        switch (outOfBoundsBehavior) {
        case DEACTIVATE:
          body.jboxBody.setActive(false);
          break;
        case DESTROY:
          remove(body);
          break;
        }
      }
    }
  }

  /**
   * Add a listener to be notified of collisions
   *
   * @param listener
   *            The listener to be notified of collisions
   */
  public void addListener(WorldListener listener) {
    listeners.add(listener);
  }

  /**
   * Remove a listener that will no longer receive events
   *
   * @param listener
   *            The listener to be removed
   */
  public void removeListener(WorldListener listener) {
    listeners.remove(listener);
  }

  /**
   * Add a listener that listens for collisions on a particular body.
   *
   * @param body
   *            body on which collisions should be detected
   * @param listener
   *            the listener to call
   */
  public void addBodyListener(Body<?> body, WorldListener listener) {
    Set<WorldListener> listeners = bodyListeners.get(body);
    if (listeners == null) {
      listeners = new HashSet<WorldListener>();
      bodyListeners.put(body, listeners);
    }
    listeners.add(listener);
  }

  /**
   * Remove a listener that listens for collisions on a particular body
   *
   * @see #addBodyListener(Body, WorldListener)
   * @param body
   * @param listener
   */
  public void removeBodyListener(Body<?> body, WorldListener listener) {
    Set<WorldListener> listeners = bodyListeners.get(body);
    if (listeners != null) {
      listeners.remove(listener);
      if (listeners.isEmpty()) {
        bodyListeners.remove(body);
      }
    }
  }

  /**
   * Establish world boundaries centered at the origin and going in
   * half-widths and half-heights in horizontal and vertical directions
   * respectively.
   *
   * @param width
   *            total width of the world
   * @param height
   *            total height of the world
   */
  public void setBounds(float width, float height) {
    setBounds(-width / 2, -height / 2, width / 2, height / 2);
  }

  /**
   * Establish world boundaries at the given coordinates.
   *
   * @param x1
   *            x-coordinate of lower-left coordinate
   * @param y1
   *            y-coordinate of lower-left coordinate
   * @param x2
   *            x-coordinate of upper-right coordinate
   * @param y2
   *            y-coordinate of upper-right coordinate
   */
  public void setBounds(float x1, float y1, float x2, float y2) {
    worldAABB = new AABB(new Vec2(x1, y1), new Vec2(x2, y2));
    outOfBoundsRegions = new AABB[] {
        // everything below-left and directly left of worldAABB
        new AABB(new Vec2(Float.MIN_VALUE, Float.MIN_VALUE), new Vec2(
            x1, y2)),
        // everything above-left and directly above worldAABB
        new AABB(new Vec2(Float.MIN_VALUE, y2), new Vec2(x2,
            Float.MAX_VALUE)),
        // everything above-right and directly right of worldAABB
        new AABB(new Vec2(x2, y1), new Vec2(Float.MAX_VALUE,
            Float.MAX_VALUE)),
        // everything below-right and directly below worldAABB
        new AABB(new Vec2(x1, Float.MIN_VALUE), new Vec2(
            Float.MAX_VALUE, y1)) };
  }

  /**
   * Return a list of all the bodies at the given position.
   * @param x world x coordinate
   * @param y world y coordinate
   * @return list of bodies at provided coordinate
   */
  public List<Body<?>> bodiesAt(float x, float y) {
    return bodiesAt(x, y, x + 1f, y + 1f);
  }

//  public List<Body<?>> bodiesAt(float x, float y, float radius) {
//    List<Body<?>> bodies = bodiesAt(x - radius, y - radius, x + radius, y
//        + radius);
//    Iterator<Body<?>> it = bodies.iterator();
//    while (it.hasNext()) {
//      Body<?> body = it.next();
//      if (Math.hypot(x - body.getX(), y - body.getY()) > radius) {
//        it.remove();
//      }
//    }
//    return bodies;
//  }

  /**
   * Return a list of all bodies in or near the given box.
   */
  public List<Body<?>> bodiesAt(float x1, float y1, float x2, float y2) {
    float lowerX, upperX, lowerY, upperY;
    if (x1 < x2) {
      lowerX = x1;
      upperX = x2;
    } else {
      lowerX = x2;
      upperX = x1;
    }
    if (y1 < y2) {
      lowerY = y1;
      upperY = y2;
    } else {
      lowerY = y2;
      upperY = y1;
    }

    AABB aabb = new AABB(new Vec2(lowerX, lowerY), new Vec2(upperX, upperY));
    final List<Body<?>> bodies = new LinkedList<Body<?>>();
    jboxWorld.queryAABB(new BodyQueryCallbackHelper(bodies), aabb);
    return bodies;
  }

  /**
   * Sets the "what happens" behavior when a body exits the world boundaries.
   * Defaults to DEACTIVATE.
   *
   * @param behavior
   *            new behavior
   */
  public void setOutOfBoundsBehavior(OutOfBoundsBehavior behavior) {
    setOutOfBoundsBehavior(behavior, null);
  }

  /**
   * Same as {@link World#setOutOfBoundsBehavior(OutOfBoundsBehavior)}, but
   * also configures a callback to run before the OutOfBoundsBehavior is
   * enforced. The callback can modify its argument to change which bodies
   * have the behavior applied, i.e. so that certain bodies aren't
   * deactivated, destroyed, etc.
   *
   * @param behavior
   * @param callback
   */
  public void setOutOfBoundsBehavior(OutOfBoundsBehavior behavior,
      OutOfBoundsCallback callback) {
    outOfBoundsBehavior = behavior;
    outOfBoundsCallback = callback;
  }

  public interface OutOfBoundsCallback {
    /**
     * Bodies that are about to be processed -- you can modify this
     * collection.
     *
     * @param bodies
     * @return false to abort regular OutOfBounds behavior
     */
    public boolean reportBodies(Collection<Body<?>> bodies);
  }

  public void setIterations(int iterations) {
    velocityIterations = positionIterations = iterations;
  }

  /**
   * Fire a notification to all listeners that a collision has occured
   *
   * @param bodyA
   *            The first body in the collision
   * @param bodyB
   *            The second body in the collision
   */
  private void fireCollision(Body<?> bodyA, Body<?> bodyB,
      FizzyContact contact) {
    CollisionEvent event = new CollisionEvent(bodyA, bodyB, contact);
    for (WorldListener listener : listeners) {
      listener.collided(event);
    }
    Collection<WorldListener> moreListeners = bodyListeners.get(bodyA);
    if (moreListeners != null) {
      for (WorldListener listener : moreListeners) {
        listener.collided(event);
      }
    }
    moreListeners = bodyListeners.get(bodyB);
    if (moreListeners != null) {
      for (WorldListener listener : moreListeners) {
        listener.collided(event);
      }
    }
  }

  /**
   * Fire a notification to all listeners that a separation has occured
   *
   * @param bodyA
   *            The first body in the separation
   * @param bodyB
   *            The second body in the separation
   */
  private void fireSeparated(Body<?> bodyA, Body<?> bodyB,
      FizzyContact contact) {
    CollisionEvent event = new CollisionEvent(bodyA, bodyB, contact);
    for (WorldListener listener : listeners) {
      listener.separated(event);
    }

    Collection<WorldListener> moreListeners = bodyListeners.get(bodyA);
    if (moreListeners != null) {
      for (WorldListener listener : moreListeners) {
        listener.separated(event);
      }
    }
    moreListeners = bodyListeners.get(bodyB);
    if (moreListeners != null) {
      for (WorldListener listener : moreListeners) {
        listener.separated(event);
      }
    }
  }

  /**
   * A contact listener to collect effects and proxy them on to world
   * listeners
   *
   * @author max
   */
  private class ProxyContactListener implements ContactListener {

    @Override
    public void beginContact(Contact contact) {
      Body<?> bodyA = shapeMap.get(contact.getFixtureA().getShape());
      Body<?> bodyB = shapeMap.get(contact.getFixtureB().getShape());

      if ((bodyA != null) && (bodyB != null)) {
        bodyA.touch(bodyB);
        bodyB.touch(bodyA);
        if (bodyA.touchCount(bodyB) == 1) {
          fireCollision(bodyA, bodyB, new FizzyContact(contact));
        }
      }
    }

    @Override
    public void endContact(Contact contact) {
      Body<?> bodyA = shapeMap.get(contact.getFixtureA().getShape());
      Body<?> bodyB = shapeMap.get(contact.getFixtureB().getShape());

      if ((bodyA != null) && (bodyB != null)) {
        bodyA.untouch(bodyB);
        bodyB.untouch(bodyA);

        if (bodyA.touchCount(bodyB) == 0) {
          fireSeparated(bodyA, bodyB, new FizzyContact(contact));
        }
      }
    }

    @Override
    public void preSolve(Contact contact, Manifold oldManifold) {
    }

    @Override
    public void postSolve(Contact contact, ContactImpulse impulse) {
    }

  }

  /**
   * Sets the world gravity.
   *
   * Negative is "down", so you'll usually want a negative value here. There
   * won't be any pull in the x-axis.
   *
   * @param gravity
   *            new value for gravity (negative for down)
   */
  public void setGravity(float gravity) {
    setGravity(0f, gravity);
  }

  /**
   * Sets x- and y-axis gravity for the world. This gives you complete control
   * over horizontal gravity in addition to vertical gravity.
   *
   * @param xGravity
   *            horizontal pull
   * @param yGravity
   *            vertical pull
   */
  public void setGravity(float xGravity, float yGravity) {
    jboxWorld.setGravity(new Vec2(xGravity, yGravity));
  }
}
TOP

Related Classes of org.newdawn.fizzy.World$OutOfBoundsCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.