
org.dyn4j.dynamics.World Maven / Gradle / Ivy
/*
* Copyright (c) 2010-2020 William Bittle http://www.dyn4j.org/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.dyn4j.dynamics;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import org.dyn4j.DataContainer;
import org.dyn4j.Listener;
import org.dyn4j.collision.Bounds;
import org.dyn4j.collision.BoundsListener;
import org.dyn4j.collision.CollisionItem;
import org.dyn4j.collision.CollisionPair;
import org.dyn4j.collision.Filter;
import org.dyn4j.collision.Fixture;
import org.dyn4j.collision.FixtureModificationHandler;
import org.dyn4j.collision.broadphase.BatchBroadphaseDetector;
import org.dyn4j.collision.broadphase.BroadphaseDetector;
import org.dyn4j.collision.broadphase.BroadphaseFilter;
import org.dyn4j.collision.broadphase.DynamicAABBTree;
import org.dyn4j.collision.continuous.ConservativeAdvancement;
import org.dyn4j.collision.continuous.TimeOfImpact;
import org.dyn4j.collision.continuous.TimeOfImpactDetector;
import org.dyn4j.collision.manifold.ClippingManifoldSolver;
import org.dyn4j.collision.manifold.Manifold;
import org.dyn4j.collision.manifold.ManifoldSolver;
import org.dyn4j.collision.narrowphase.Gjk;
import org.dyn4j.collision.narrowphase.LinkPostProcessor;
import org.dyn4j.collision.narrowphase.NarrowphaseDetector;
import org.dyn4j.collision.narrowphase.NarrowphasePostProcessor;
import org.dyn4j.collision.narrowphase.Penetration;
import org.dyn4j.collision.narrowphase.Raycast;
import org.dyn4j.collision.narrowphase.RaycastDetector;
import org.dyn4j.dynamics.contact.Contact;
import org.dyn4j.dynamics.contact.ContactConstraint;
import org.dyn4j.dynamics.contact.ContactConstraintSolver;
import org.dyn4j.dynamics.contact.ContactListener;
import org.dyn4j.dynamics.contact.ContactManager;
import org.dyn4j.dynamics.contact.ContactPoint;
import org.dyn4j.dynamics.contact.DefaultContactManager;
import org.dyn4j.dynamics.contact.SequentialImpulses;
import org.dyn4j.dynamics.contact.ForceCollisionTimeOfImpactSolver;
import org.dyn4j.dynamics.joint.Joint;
import org.dyn4j.geometry.AABB;
import org.dyn4j.geometry.Convex;
import org.dyn4j.geometry.Ray;
import org.dyn4j.geometry.Shiftable;
import org.dyn4j.geometry.Transform;
import org.dyn4j.geometry.Vector2;
import org.dyn4j.resources.Messages;
/**
* Manages the logic of collision detection, resolution, and reporting.
*
* Interfacing with dyn4j starts with this class. Create a new instance of this class
* and add bodies and joints. Then call one of the update or step methods in your game
* loop to move the physics engine forward in time.
*
* Via the {@link #addListener(Listener)} method, a {@link World} instance can have multiple listeners for all the listener types.
* Some listener types return a boolean to indicate continuing or allowing something, like {@link CollisionListener}. If, for example,
* there are multiple {@link CollisionListener}s and any one of them returns false for an event, the collision is skipped. However,
* all listeners will still be called no matter if the first returned false.
* @author William Bittle
* @version 4.0.0
* @since 1.0.0
* @deprecated Deprecated in 4.0.0. Use the World class in the world package instead.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Deprecated
public class World implements Shiftable, DataContainer {
/** Identity Transform instance */
private static final Transform IDENTITY = new Transform();
/** Earths gravity constant */
public static final Vector2 EARTH_GRAVITY = new Vector2(0.0, -9.8);
/** Zero gravity constant */
public static final Vector2 ZERO_GRAVITY = new Vector2(0.0, 0.0);
// settings
/** The dynamics settings for this world */
protected Settings settings;
/** The {@link Step} used by the dynamics calculations */
protected Step step;
/** The world gravity vector */
protected Vector2 gravity;
/** The world {@link Bounds} */
protected Bounds bounds;
// algorithms
/** The {@link BroadphaseDetector} */
protected BroadphaseDetector
broadphaseDetector;
/** The {@link BroadphaseFilter} for detection */
protected BroadphaseFilter detectBroadphaseFilter;
/** The {@link NarrowphaseDetector} */
protected NarrowphaseDetector narrowphaseDetector;
/** The {@link NarrowphasePostProcessor} */
protected NarrowphasePostProcessor narrowphasePostProcessor;
/** The {@link ManifoldSolver} */
protected ManifoldSolver manifoldSolver;
/** The {@link TimeOfImpactDetector} */
protected TimeOfImpactDetector timeOfImpactDetector;
/** The {@link RaycastDetector} */
protected RaycastDetector raycastDetector;
/** The {@link ContactManager} */
protected ContactManager contactManager;
/** The {@link CoefficientMixer} */
protected CoefficientMixer coefficientMixer;
/** The {@link ContactConstraintSolver} */
protected ContactConstraintSolver contactConstraintSolver;
/** The {@link ForceCollisionTimeOfImpactSolver} */
protected ForceCollisionTimeOfImpactSolver timeOfImpactSolver;
/** The application data associated */
protected Object userData;
// internal
// listeners and config
/** The list of listeners for this world */
private final List listeners;
// bodies/joints
/** The {@link Body} list */
private final List bodies;
/** The {@link Joint} list */
private final List joints;
// temp data
/** The reusable island */
private Island island;
/** The accumulated time */
private double time;
/** Flag to find new contacts */
private boolean updateRequired;
/**
* Default constructor.
*
* Builds a simulation {@link World} without bounds.
*
* Defaults to using {@link #EARTH_GRAVITY}, {@link DynamicAABBTree} broad-phase,
* {@link Gjk} narrow-phase, and {@link ClippingManifoldSolver}.
*
* Uses the {@link Capacity#DEFAULT_CAPACITY} capacity object for initialization.
*/
public World() {
this(Capacity.DEFAULT_CAPACITY, null);
}
/**
* Optional constructor.
*
* Defaults to using {@link #EARTH_GRAVITY}, {@link DynamicAABBTree} broad-phase,
* {@link Gjk} narrow-phase, and {@link ClippingManifoldSolver}.
*
* The initial capacity specifies the estimated number of bodies that the simulation
* will have at any one time. This is used to size internal structures to improve
* performance. The internal structures can grow past the initial capacity.
* @param initialCapacity the initial capacity settings
* @since 3.1.1
*/
public World(Capacity initialCapacity) {
this(initialCapacity, null);
}
/**
* Optional constructor.
*
* Defaults to using {@link #EARTH_GRAVITY}, {@link DynamicAABBTree} broad-phase,
* {@link Gjk} narrow-phase, and {@link ClippingManifoldSolver}.
* @param bounds the bounds of the {@link World}; can be null
*/
public World(Bounds bounds) {
this(Capacity.DEFAULT_CAPACITY, bounds);
}
/**
* Full constructor.
*
* Defaults to using {@link #EARTH_GRAVITY}, {@link DynamicAABBTree} broad-phase,
* {@link Gjk} narrow-phase, and {@link ClippingManifoldSolver}.
*
* The initial capacity specifies the estimated number of bodies that the simulation
* will have at any one time. This is used to size internal structures to improve
* performance. The internal structures can grow past the initial capacity.
* @param initialCapacity the initial capacity settings
* @param bounds the bounds of the {@link World}; can be null
* @throws NullPointerException if initialCapacity is null
* @since 3.1.1
*/
public World(Capacity initialCapacity, Bounds bounds) {
// check for null capacity
if (initialCapacity == null) initialCapacity = Capacity.DEFAULT_CAPACITY;
// initialize all the classes with default values
this.settings = new Settings();
this.step = new Step(this.settings.getStepFrequency());
this.gravity = World.EARTH_GRAVITY;
this.bounds = bounds;
this.broadphaseDetector = new DynamicAABBTree
(initialCapacity.getBodyCount());
this.detectBroadphaseFilter = new DetectBroadphaseFilter();
this.narrowphaseDetector = new Gjk();
this.narrowphasePostProcessor = new LinkPostProcessor();
this.manifoldSolver = new ClippingManifoldSolver();
this.timeOfImpactDetector = new ConservativeAdvancement();
this.raycastDetector = new Gjk();
this.coefficientMixer = CoefficientMixer.DEFAULT_MIXER;
this.contactManager = new DefaultContactManager(initialCapacity);
this.contactConstraintSolver = new SequentialImpulses();
this.timeOfImpactSolver = new ForceCollisionTimeOfImpactSolver();
this.bodies = new ArrayList(initialCapacity.getBodyCount());
this.joints = new ArrayList(initialCapacity.getJointCount());
this.listeners = new ArrayList(initialCapacity.getListenerCount());
this.island = new Island(initialCapacity);
this.time = 0.0;
this.updateRequired = true;
}
/**
* Updates the {@link World}.
*
* This method will only update the world given the step frequency contained
* in the {@link Settings} object. You can use the {@link StepListener} interface
* to listen for when a step is actually performed. In addition, this method will
* return true if a step was performed.
*
* This method performs, at maximum, one simulation step. Any remaining time from
* the previous call of this method is added to the given elapsed time to determine
* if a step needs to be performed. If the given elapsed time is usually greater
* than the step frequency, consider using the {@link #update(double, int)} method
* instead.
*
* Alternatively you can call the {@link #updatev(double)} method to use a variable
* time step.
* @see #update(double, int)
* @see #updatev(double)
* @see #getAccumulatedTime()
* @param elapsedTime the elapsed time in seconds
* @return boolean true if the {@link World} performed a simulation step
*/
public boolean update(double elapsedTime) {
return this.update(elapsedTime, -1.0, 1);
}
/**
* Updates the {@link World}.
*
* This method will only update the world given the step frequency contained
* in the {@link Settings} object. You can use the {@link StepListener} interface
* to listen for when a step is actually performed.
*
* Unlike the {@link #update(double)} method, this method will perform more than one
* step based on the given elapsed time. For example, if the given elapsed time + the
* remaining time from the last call of this method is 2 * step frequency, then 2 steps
* will be performed. Use the maximumSteps parameter to put an upper bound on the
* number of steps performed.
*
* Alternatively you can call the {@link #updatev(double)} method to use a variable
* time step.
* @see #update(double)
* @see #updatev(double)
* @see #getAccumulatedTime()
* @param elapsedTime the elapsed time in seconds
* @param maximumSteps the maximum number of steps to perform
* @return boolean true if the {@link World} performed at least one simulation step
* @since 3.1.10
*/
public boolean update(double elapsedTime, int maximumSteps) {
return this.update(elapsedTime, -1.0, maximumSteps);
}
/**
* Updates the {@link World}.
*
* This method will only update the world given the step frequency contained
* in the {@link Settings} object. You can use the {@link StepListener} interface
* to listen for when a step is actually performed. In addition, this method will
* return true if a step was performed.
*
* This method performs, at maximum, one simulation step. Any remaining time from
* the previous call of this method is added to the given elapsed time to determine
* if a step needs to be performed. If the given elapsed time is usually greater
* than the step frequency, consider using the {@link #update(double, int)} method
* instead.
*
* The stepElapsedTime parameter provides a way for the {@link World} to continue to
* update at the frequency defined in the {@link Settings} object, but advance the
* simulation by the given time.
*
* Alternatively you can call the {@link #updatev(double)} method to use a variable
* time step.
* @see #update(double)
* @see #updatev(double)
* @see #getAccumulatedTime()
* @param elapsedTime the elapsed time in seconds
* @param stepElapsedTime the time, in seconds, that the simulation should be advanced
* @return boolean true if the {@link World} performed at least one simulation step
* @since 3.2.4
*/
public boolean update(double elapsedTime, double stepElapsedTime) {
return this.update(elapsedTime, stepElapsedTime, 1);
}
/**
* Updates the {@link World}.
*
* This method will only update the world given the step frequency contained
* in the {@link Settings} object. You can use the {@link StepListener} interface
* to listen for when a step is actually performed.
*
* Unlike the {@link #update(double)} method, this method will perform more than one
* step based on the given elapsed time. For example, if the given elapsed time + the
* remaining time from the last call of this method is 2 * step frequency, then 2 steps
* will be performed. Use the maximumSteps parameter to put an upper bound on the
* number of steps performed.
*
* The stepElapsedTime parameter provides a way for the {@link World} to continue to
* update at the frequency defined in the {@link Settings} object, but advance the
* simulation by the given time.
*
* Alternatively you can call the {@link #updatev(double)} method to use a variable
* time step.
* @see #update(double)
* @see #updatev(double)
* @see #getAccumulatedTime()
* @param elapsedTime the elapsed time in seconds
* @param stepElapsedTime the time, in seconds, that the simulation should be advanced for each step; if less than or equal to zero {@link Settings#getStepFrequency()} will be used
* @param maximumSteps the maximum number of steps to perform
* @return boolean true if the {@link World} performed at least one simulation step
* @since 3.2.4
*/
public boolean update(double elapsedTime, double stepElapsedTime, int maximumSteps) {
// make sure the update time is greater than zero
if (elapsedTime < 0.0) elapsedTime = 0.0;
// update the time
this.time += elapsedTime;
// check the frequency in settings
double invhz = this.settings.getStepFrequency();
// see if we should update or not
int steps = 0;
while (this.time >= invhz && steps < maximumSteps) {
// update the step
this.step.update(stepElapsedTime <= 0 ? invhz : stepElapsedTime);
// reset the time
this.time = this.time - invhz;
// step the world
this.step();
// increment the number of steps
steps++;
}
return steps > 0;
}
/**
* Updates the {@link World}.
*
* This method will update the world on every call. Unlike the {@link #update(double)}
* method, this method uses the given elapsed time and does not attempt to update the world
* on a set interval.
*
* This method immediately returns if the given elapsedTime is less than or equal to
* zero.
* @see #update(double)
* @see #update(double, int)
* @param elapsedTime the elapsed time in seconds
*/
public void updatev(double elapsedTime) {
// make sure the update time is greater than zero
if (elapsedTime <= 0.0) return;
// update the step
this.step.update(elapsedTime);
// step the world
this.step();
}
/**
* Performs the given number of simulation steps using the step frequency in {@link Settings}.
*
* This method immediately returns if the given step count is less than or equal to
* zero.
* @param steps the number of simulation steps to perform
*/
public void step(int steps) {
// get the frequency from settings
double invhz = this.settings.getStepFrequency();
// perform the steps
this.step(steps, invhz);
}
/**
* Performs the given number of simulation steps using the given elapsed time for each step.
*
* This method immediately returns if the given elapsedTime or step count is less than or equal to
* zero.
* @param steps the number of simulation steps to perform
* @param elapsedTime the elapsed time for each step
*/
public void step(int steps, double elapsedTime) {
// make sure the number of steps is greather than zero
if (steps <= 0) return;
// make sure the update time is greater than zero
if (elapsedTime <= 0.0) return;
// perform the steps
for (int i = 0; i < steps; i++) {
// update the step object
this.step.update(elapsedTime);
// step the world
this.step();
}
}
/**
* Performs one time step of the {@link World} using the current {@link Step}.
*
* This method advances the world by the elapsed time in the {@link Step} object
* and performs collision resolution and constraint solving.
*
* This method will perform a collision detection sweep at the end to ensure that
* callers of the world have the latest collision information. If the {@link #isUpdateRequired()}
* method returns true, a collision detection sweep will be performed before doing
* collision resolution. See the {@link #setUpdateRequired(boolean)} method for details
* on when this flag should be set.
*
* Use the various listeners to listen for events during the execution of
* this method.
*
* If possible use the {@link StepListener#postSolve(Step, World)} method to update any
* bodies or joints to increase performance.
*
* Most {@link Listener}s do not allow modification of the world, bodies, joints, etc in
* there methods. It's recommended that any of modification be performed in a {@link StepListener}
* or after this method has returned.
*/
protected void step() {
// get all the step listeners
List stepListeners = this.getListeners(StepListener.class);
List contactListeners = this.getListeners(ContactListener.class);
int sSize = stepListeners.size();
// notify the step listeners
for (int i = 0; i < sSize; i++) {
StepListener sl = stepListeners.get(i);
sl.begin(this.step, this);
}
// check if we need to update the contacts first
if (this.updateRequired) {
// if so then update the contacts
this.detect();
// notify that an update was performed
for (int i = 0; i < sSize; i++) {
StepListener sl = stepListeners.get(i);
sl.updatePerformed(this.step, this);
}
// set the update required flag to false
this.updateRequired = false;
}
// notify of all the contacts that will be solved and all the sensed contacts
this.contactManager.preSolveNotify(contactListeners);
// check for CCD
ContinuousDetectionMode continuousDetectionMode = this.settings.getContinuousDetectionMode();
// get the number of bodies
int size = this.bodies.size();
// test for out of bounds objects
// clear the body contacts
// clear the island flag
// save the current transform for CCD
for (int i = 0; i < size; i++) {
Body body = this.bodies.get(i);
// remove the island flag
body.setOnIsland(false);
// save the current transform into the previous transform
body.getInitialTransform().set(body.getTransform());
}
// clear the joint island flags
int jSize = this.joints.size();
for (int i = 0; i < jSize; i++) {
// get the joint
Joint joint = this.joints.get(i);
// set the island flag to false
joint.setOnIsland(false);
}
// perform a depth first search of the contact graph
// to create islands for constraint solving
Deque stack = new ArrayDeque(size);
// temp storage
// we put these here so we can implicitly convert from joint and
// contact constraint to constraint so that we have package private
// access to the isOnIsland and setOnIsland methods
Joint joint;
ContactConstraint contactConstraint;
// Constraint constraint;
// loop over the bodies and their contact edges to create the islands
for (int i = 0; i < size; i++) {
Body seed = this.bodies.get(i);
// skip if asleep, in active, static, or already on an island
if (seed.isOnIsland() || seed.isAsleep() || !seed.isEnabled() || seed.isStatic()) continue;
// set the island to the reusable island
Island island = this.island;
island.clear();
stack.clear();
stack.push(seed);
while (stack.size() > 0) {
// get the next body
Body body = stack.pop();
// add it to the island
island.add(body);
// flag that it has been added
body.setOnIsland(true);
// make sure the body is awake
body.setAsleep(false);
// if its static then continue since we dont want the
// island to span more than one static object
// this keeps the size of the islands small
if (body.isStatic()) continue;
// loop over the contact edges of this body
int ceSize = body.contacts.size();
for (int j = 0; j < ceSize; j++) {
ContactEdge contactEdge = body.contacts.get(j);
// get the contact constraint
contactConstraint = contactEdge.interaction;
// skip sensor contacts
// check if the contact constraint has already been added to an island
if (!contactConstraint.isEnabled() || contactConstraint.isSensor() || contactConstraint.isOnIsland()) continue;
// get the other body
Body other = contactEdge.other;
// add the contact constraint to the island list
island.add(contactConstraint);
// set the island flag on the contact constraint
contactConstraint.setOnIsland(true);
// has the other body been added to an island yet?
if (!other.isOnIsland()) {
// if not then add this body to the stack
stack.push(other);
other.setOnIsland(true);
}
}
// loop over the joint edges of this body
int jeSize = body.joints.size();
for (int j = 0; j < jeSize; j++) {
// get the joint edge
JointEdge jointEdge = body.joints.get(j);
// get the joint
joint = jointEdge.interaction;
// check if the joint is inactive
if (!joint.isActive() || joint.isOnIsland()) continue;
// get the other body
Body other = jointEdge.other;
// check if the joint has already been added to an island
// or if the other body is not active
if (!other.isEnabled()) continue;
// add the joint to the island
island.add(joint);
// set the island flag on the joint
joint.setOnIsland(true);
// check if the other body has been added to an island
if (!other.isOnIsland()) {
// if not then add the body to the stack
stack.push(other);
other.setOnIsland(true);
}
}
}
// solve the island
island.solve(this.contactConstraintSolver, this.gravity, this.step, this.settings);
// allow static bodies to participate in other islands
int isize = island.bodies.size();
for (int j = 0; j < isize; j++) {
Body body = island.bodies.get(j);
if (body.isStatic()) {
body.setOnIsland(false);
}
}
}
// allow memory to be reclaimed
stack.clear();
this.island.clear();
// notify of the all solved contacts
this.contactManager.postSolveNotify(contactListeners);
// make sure CCD is enabled
if (continuousDetectionMode != ContinuousDetectionMode.NONE) {
// solve time of impact
this.solveTOI(continuousDetectionMode);
}
// notify the step listener
for (int i = 0; i < sSize; i++) {
StepListener sl = stepListeners.get(i);
sl.postSolve(this.step, this);
}
// after all has been updated find new contacts
// this is done so that the user has the latest contacts
// and the broadphase has the latest AABBs, etc.
this.detect();
// set the update required flag to false
this.updateRequired = false;
// notify the step listener
for (int i = 0; i < sSize; i++) {
StepListener sl = stepListeners.get(i);
sl.end(this.step, this);
}
}
/**
* Finds new contacts for all bodies in this world.
*
* This method performs the following:
*
* - Checks for out of bound bodies
* - Updates the broad-phase using the current body positions
* - Performs broad-phase collision detection
* - Performs narrow-phase collision detection
* - Performs manifold solving
* - Adds contacts to the contact manager
* - Warm starts the contacts
*
*
* This method will notify all bounds and collision listeners. If any {@link CollisionListener}
* returns false, the collision is ignored.
*
* This method also notifies any {@link ContactListener}s.
* @since 3.0.0
*/
protected void detect() {
// get the bounds listeners
List boundsListeners = this.getListeners(BoundsListener.class);
List collisionListeners = this.getListeners(CollisionListener.class);
// get the number of bodies
int size = this.bodies.size();
int blSize = boundsListeners.size();
int clSize = collisionListeners.size();
// test for out of bounds objects
// clear the body contacts
// update the broadphase
// Check if the current broad-phase detector support batch updates, and use it if so
if (this.broadphaseDetector instanceof BatchBroadphaseDetector, ?>) {
for (int i = 0; i < size; i++) {
Body body = this.bodies.get(i);
// skip if already not active
if (!body.isEnabled()) continue;
// clear all the old contacts
body.contacts.clear();
// check if bounds have been set
// check if the body is out of bounds
if (this.bounds != null && this.bounds.isOutside(body)) {
// set the body to inactive
body.setEnabled(false);
// if so, notify via the listeners
for (int j = 0; j < blSize; j++) {
BoundsListener bl = boundsListeners.get(j);
bl.outside(body);
}
}
}
((BatchBroadphaseDetector, ?>) this.broadphaseDetector).batchUpdate();
} else {
// Else update each body separately
for (int i = 0; i < size; i++) {
Body body = this.bodies.get(i);
// skip if already not active
if (!body.isEnabled()) continue;
// clear all the old contacts
body.contacts.clear();
// check if bounds have been set
// check if the body is out of bounds
if (this.bounds != null && this.bounds.isOutside(body)) {
// set the body to inactive
body.setEnabled(false);
// if so, notify via the listeners
for (int j = 0; j < blSize; j++) {
BoundsListener bl = boundsListeners.get(j);
bl.outside(body);
}
}
// update the broadphase with the new position/orientation
this.broadphaseDetector.update(body);
}
}
// make sure there are some bodies
if (size > 0) {
// test for collisions via the broad-phase
List> pairs = this.broadphaseDetector.detect(this.detectBroadphaseFilter);
int pSize = pairs.size();
boolean allow = true;
// using the broad-phase results, test for narrow-phase
for (int i = 0; i < pSize; i++) {
CollisionPair pair = pairs.get(i);
// get the bodies
Body body1 = pair.getBody1();
Body body2 = pair.getBody2();
BodyFixture fixture1 = pair.getFixture1();
BodyFixture fixture2 = pair.getFixture2();
allow = true;
for (int j = 0; j < clSize; j++) {
CollisionListener cl = collisionListeners.get(j);
if (!cl.collision(body1, fixture1, body2, fixture2)) {
// if any collision listener returned false then skip this collision
// we must allow all the listeners to get notified first, then skip
// the collision
allow = false;
}
}
if (!allow) continue;
// get their transforms
Transform transform1 = body1.getTransform();
Transform transform2 = body2.getTransform();
Convex convex2 = fixture2.getShape();
Convex convex1 = fixture1.getShape();
Penetration penetration = new Penetration();
// test the two convex shapes
if (this.narrowphaseDetector.detect(convex1, transform1, convex2, transform2, penetration)) {
// check for zero penetration
if (penetration.getDepth() == 0.0) {
// this should only happen if numerical error occurs
continue;
}
// perform post processing
if (this.narrowphasePostProcessor != null) {
this.narrowphasePostProcessor.process(convex1, transform1, convex2, transform2, penetration);
}
// notify of the narrow-phase collision
allow = true;
for (int j = 0; j < clSize; j++) {
CollisionListener cl = collisionListeners.get(j);
if (!cl.collision(body1, fixture1, body2, fixture2, penetration)) {
// if any collision listener returned false then skip this collision
// we must allow all the listeners to get notified first, then skip
// the collision
allow = false;
}
}
if (!allow) continue;
Manifold manifold = new Manifold();
// if there is penetration then find a contact manifold
// using the filled in penetration object
if (this.manifoldSolver.getManifold(penetration, convex1, transform1, convex2, transform2, manifold)) {
// check for zero points
if (manifold.getPoints().size() == 0) {
// this should only happen if numerical error occurs
continue;
}
// notify of the manifold solving result
allow = true;
for (int j = 0; j < clSize; j++) {
CollisionListener cl = collisionListeners.get(j);
if (!cl.collision(body1, fixture1, body2, fixture2, manifold)) {
// if any collision listener returned false then skip this collision
// we must allow all the listeners to get notified first, then skip
// the collision
allow = false;
}
}
if (!allow) continue;
// create a contact constraint
ContactConstraint contactConstraint = new ContactConstraint(pair,
manifold,
this.coefficientMixer.mixFriction(fixture1.getFriction(), fixture2.getFriction()),
this.coefficientMixer.mixRestitution(fixture1.getRestitution(), fixture2.getRestitution()));
allow = true;
// notify of the created contact constraint
for (int j = 0; j < clSize; j++) {
CollisionListener cl = collisionListeners.get(j);
if (!cl.collision(contactConstraint)) {
// if any collision listener returned false then skip this collision
// we must allow all the listeners to get notified first, then skip
// the collision
allow = false;
}
}
if (!allow) continue;
// add a contact edge to both bodies
ContactEdge contactEdge1 = new ContactEdge(body2, contactConstraint);
ContactEdge contactEdge2 = new ContactEdge(body1, contactConstraint);
body1.contacts.add(contactEdge1);
body2.contacts.add(contactEdge2);
// add the contact constraint to the contact manager
this.contactManager.queue(contactConstraint);
}
}
}
}
// warm start the contact constraints
this.contactManager.updateAndNotify(this.getListeners(ContactListener.class), this.settings);
this.broadphaseDetector.clearUpdates();
}
/**
* Solves the time of impact for all the {@link Body}s in this {@link World}.
*
* This method solves for the time of impact for each {@link Body} iteratively
* and pairwise.
*
* The cases considered are dependent on the given collision detection mode.
*
* Cases skipped (including the converse of the above):
*
* - Inactive, asleep, or non-moving bodies
* - Bodies connected via a joint with the collision flag set to false
* - Bodies already in contact
* - Fixtures whose filters return false
* - Sensor fixtures
*
* @param mode the continuous collision detection mode
* @see ContinuousDetectionMode
* @since 1.2.0
*/
protected void solveTOI(ContinuousDetectionMode mode) {
List listeners = this.getListeners(TimeOfImpactListener.class);
// get the number of bodies
int size = this.bodies.size();
// check the CCD mode
boolean bulletsOnly = (mode == ContinuousDetectionMode.BULLETS_ONLY);
// loop over all the bodies and find the minimum TOI for each
// dynamic body
for (int i = 0; i < size; i++) {
// get the body
Body body = this.bodies.get(i);
// if we are only doing CCD on bullets only, then check
// to make sure that the current body is a bullet
if (bulletsOnly && !body.isBullet()) continue;
// otherwise we process all dynamic bodies
// we don't process kinematic or static bodies except with
// dynamic bodies (in other words b1 must always be a dynamic
// body)
if (body.mass.isInfinite()) continue;
// don't bother with bodies that did not have their
// positions integrated, if they were not added to an island then
// that means they didn't move
// we can also check for sleeping bodies and skip those since
// they will only be asleep after being stationary for a set
// time period
if (!body.isOnIsland() || body.isAsleep()) continue;
// solve for time of impact
this.solveTOI(body, listeners);
}
}
/**
* Solves the time of impact for the given {@link Body}.
*
* This method will find the first {@link Body} that the given {@link Body}
* collides with unless ignored via the {@link TimeOfImpactListener}.
*
* If any {@link TimeOfImpactListener} doesn't allow the collision then the collision
* is ignored.
*
* After the first {@link Body} is found the two {@link Body}s are interpolated
* to the time of impact.
*
* Then the {@link Body}s are position solved using the {@link ForceCollisionTimeOfImpactSolver}
* to force the {@link Body}s into collision. This causes the discrete collision
* detector to detect the collision on the next time step.
* @param body1 the {@link Body}
* @param listeners the list of {@link TimeOfImpactListener}s
* @since 3.1.0
*/
protected void solveTOI(Body body1, List listeners) {
int size = this.bodies.size();
// generate a swept AABB for this body
AABB aabb1 = body1.createSweptAABB();
boolean bullet = body1.isBullet();
// setup the initial time bounds [0, 1]
double t1 = 0.0;
double t2 = 1.0;
// save the minimum time of impact and body
TimeOfImpact minToi = null;
Body minBody = null;
// loop over all the other bodies to find the minimum TOI
for (int i = 0; i < size; i++) {
// get the other body
Body body2 = this.bodies.get(i);
// skip this test if they are the same body
if (body1 == body2) continue;
// make sure the other body is active
if (!body2.isEnabled()) continue;
// skip other dynamic bodies; we only do TOI for
// dynamic vs. static/kinematic unless its a bullet
if (body2.isDynamic() && !bullet) continue;
// check for connected pairs who's collision is not allowed
if (body1.isConnected(body2, false)) continue;
// check for bodies already in collision
if (body1.isInContact(body2)) continue;
// create a swept AABB for the other body
AABB aabb2 = body2.createSweptAABB();
// if the swept AABBs don't overlap then don't bother testing them
if (!aabb1.overlaps(aabb2)) continue;
TimeOfImpact toi = new TimeOfImpact();
int fc1 = body1.getFixtureCount();
int fc2 = body2.getFixtureCount();
// get the velocities for the time step since we want
// [t1, t2] to be bound to this time step
double dt = this.step.getDeltaTime();
// the linear and angular velocities should match what
// we did when we advanced the position. alternatively
// we could calculate these from the start and end transforms
// but this has the problem of not knowing which direction
// the angular velocity is going (clockwise or anti-clockwise).
// however, this also has the problem of being different that
// the way the bodies are advanced in the Island solving
// (for now they are the same, but could be changed in the
// future).
Vector2 v1 = body1.getLinearVelocity().product(dt);
Vector2 v2 = body2.getLinearVelocity().product(dt);
double av1 = body1.getAngularVelocity() * dt;
double av2 = body2.getAngularVelocity() * dt;
Transform tx1 = body1.getInitialTransform();
Transform tx2 = body2.getInitialTransform();
// test against all fixture pairs taking the fixture
// with the smallest time of impact
for (int j = 0; j < fc1; j++) {
BodyFixture f1 = body1.getFixture(j);
// skip sensor fixtures
if (f1.isSensor()) continue;
for (int k = 0; k < fc2; k++) {
BodyFixture f2 = body2.getFixture(k);
// skip sensor fixtures
if (f2.isSensor()) continue;
Filter filter1 = f1.getFilter();
Filter filter2 = f2.getFilter();
// make sure the fixture filters allow the collision
if (!filter1.isAllowed(filter2)) {
continue;
}
Convex c1 = f1.getShape();
Convex c2 = f2.getShape();
// get the time of impact for the fixture pair
if (this.timeOfImpactDetector.getTimeOfImpact(c1, tx1, v1, av1, c2, tx2, v2, av2, t1, t2, toi)) {
// get the time of impact
double t = toi.getTime();
// check if the time of impact is less than
// the current time of impact
if (t < t2) {
// if it is then ask the listeners if we should use this collision
boolean allow = true;
for (TimeOfImpactListener tl : listeners) {
if (!tl.collision(body1, f1, body2, f2, toi)) {
// if any toi listener doesnt allow it, then don't allow it
// we need to allow all listeners to be notified before we continue
allow = false;
}
}
if (allow) {
// set the new upper bound
t2 = t;
// save the minimum toi and body
minToi = toi;
minBody = body2;
}
}
}
}
}
// if the bodies are intersecting or do not intersect
// within the range of motion then skip this body
// and move to the next
}
// make sure the time of impact is not null
if (minToi != null) {
// get the time of impact info
double t = minToi.getTime();
// move the dynamic body to the time of impact
body1.getInitialTransform().lerp(body1.getTransform(), t, body1.getTransform());
// check if the other body is dynamic
if (minBody.isDynamic()) {
// if the other body is dynamic then interpolate its transform also
minBody.getInitialTransform().lerp(minBody.getTransform(), t, minBody.getTransform());
}
// this should bring the bodies within d distance from one another
// we need to move the bodies more so that they are in collision
// so that on the next time step they are solved by the discrete
// collision detector
// performs position correction on the body/bodies so that they are
// in collision and will be detected in the next time step
this.timeOfImpactSolver.solve(body1, minBody, minToi, this.settings);
// this method does not conserve time
}
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
*
* Inactive bodies are ignored in this test.
* @param start the start point
* @param end the end point
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the {@link Ray}
* @throws NullPointerException if start, end, or results is null
* @see #raycast(Ray, double, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 2.0.0
*/
public boolean raycast(Vector2 start, Vector2 end, boolean ignoreSensors, boolean all, List results) {
return this.raycast(start, end, null, ignoreSensors, true, all, results);
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
* @param start the start point
* @param end the end point
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the {@link Ray}
* @throws NullPointerException if start, end, or results is null
* @see #raycast(Ray, double, boolean, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Vector2 start, Vector2 end, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
return this.raycast(start, end, null, ignoreSensors, ignoreInactive, all, results);
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
* @param start the start point
* @param end the end point
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the {@link Ray}
* @throws NullPointerException if start, end, or results is null
* @see #raycast(Ray, double, Filter, boolean, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Vector2 start, Vector2 end, Filter filter, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
// create the ray and obtain the maximum length
Vector2 d = start.to(end);
double maxLength = d.normalize();
Ray ray = new Ray(start, d);
// call the raycast method
return this.raycast(ray, maxLength, filter, ignoreSensors, ignoreInactive, all, results);
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* Pass 0 into the maxLength field to specify an infinite length {@link Ray}.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
*
* Inactive bodies are ignored in this test.
* @param ray the {@link Ray}
* @param maxLength the maximum length of the ray; 0 for infinite length
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the given {@link Ray}
* @throws NullPointerException if ray or results is null
* @see #raycast(Vector2, Vector2, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 2.0.0
*/
public boolean raycast(Ray ray, double maxLength, boolean ignoreSensors, boolean all, List results) {
return this.raycast(ray, maxLength, null, ignoreSensors, true, all, results);
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* Pass 0 into the maxLength field to specify an infinite length {@link Ray}.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
* @param ray the {@link Ray}
* @param maxLength the maximum length of the ray; 0 for infinite length
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the given {@link Ray}
* @throws NullPointerException if ray or results is null
* @see #raycast(Vector2, Vector2, boolean, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Ray ray, double maxLength, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
return this.raycast(ray, maxLength, null, ignoreSensors, ignoreInactive, all, results);
}
/**
* Performs a raycast against all the {@link Body}s in the {@link World}.
*
* The given {@link RaycastResult} list, results, will be filled with the raycast results
* if the given ray intersected any bodies.
*
* The {@link RaycastResult} class implements the Comparable interface to allow sorting by
* distance from the ray's origin.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* Pass 0 into the maxLength field to specify an infinite length {@link Ray}.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Bodies that contain the start of the ray will not be included in the results.
* @param ray the {@link Ray}
* @param maxLength the maximum length of the ray; 0 for infinite length
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor {@link BodyFixture}s should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param all true if all intersected {@link Body}s should be returned; false if only the closest {@link Body} should be returned
* @param results a list to contain the results of the raycast
* @return boolean true if at least one {@link Body} was intersected by the given {@link Ray}
* @throws NullPointerException if ray or results is null
* @see #raycast(Vector2, Vector2, Filter, boolean, boolean, boolean, List)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Ray ray, double maxLength, Filter filter, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
List listeners = this.getListeners(RaycastListener.class);
int rlSize = listeners.size();
// check for the desired length
double max = 0.0;
if (maxLength > 0.0) {
max = maxLength;
}
// create a raycast result
RaycastResult result = null;
RaycastBroadphaseFilter bpFilter = new RaycastBroadphaseFilter(ignoreInactive, ignoreSensors, filter);
// filter using the broadphase first
List> items = this.broadphaseDetector.raycast(ray, maxLength, bpFilter);
// loop over the list of bodies testing each one
int size = items.size();
boolean found = false;
boolean allow = true;
for (int i = 0; i < size; i++) {
// get a body to test
CollisionItem item = items.get(i);
Body body = item.getBody();
BodyFixture fixture = item.getFixture();
Transform transform = body.getTransform();
// create a raycast object to store the result
Raycast raycast = new Raycast();
// notify the listeners to see if we should test this fixture
allow = true;
for (int j = 0; j < rlSize; j++) {
RaycastListener rl = listeners.get(j);
// see if we should test this fixture
if (!rl.allow(ray, body, fixture)) {
allow = false;
}
}
if (!allow) continue;
// get the convex shape
Convex convex = fixture.getShape();
// perform the raycast
if (this.raycastDetector.raycast(ray, max, convex, transform, raycast)) {
// notify the listeners to see if we should allow this result
allow = true;
for (int j = 0; j < rlSize; j++) {
RaycastListener rl = listeners.get(j);
// see if we should test this fixture
if (!rl.allow(ray, body, fixture, raycast)) {
allow = false;
}
}
if (!allow) continue;
if (!all) {
if (result == null) {
result = new RaycastResult(body, fixture, raycast);
results.add(result);
found = true;
} else {
result.body = body;
result.fixture = fixture;
result.raycast = raycast;
}
// we are only looking for the closest so
// set the new maximum
max = result.raycast.getDistance();
} else {
// add this result to the results
results.add(new RaycastResult(body, fixture, raycast));
found = true;
}
}
}
return found;
}
/**
* Performs a raycast against the given {@link Body} and returns true
* if the ray intersects the body.
*
* The given {@link RaycastResult} object, result, will be filled with the raycast result
* if the given ray intersected the given body.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Returns false if the start position of the ray lies inside the given body.
* @param start the start point
* @param end the end point
* @param body the {@link Body} to test
* @param ignoreSensors whether or not to ignore sensor {@link BodyFixture}s
* @param result the raycast result
* @return boolean true if the {@link Ray} intersects the {@link Body}
* @throws NullPointerException if start, end, body, or result is null
* @see #raycast(Ray, Body, double, boolean, RaycastResult)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 2.0.0
*/
public boolean raycast(Vector2 start, Vector2 end, Body body, boolean ignoreSensors, RaycastResult result) {
return this.raycast(start, end, body, null, ignoreSensors, result);
}
/**
* Performs a raycast against the given {@link Body} and returns true
* if the ray intersects the body.
*
* The given {@link RaycastResult} object, result, will be filled with the raycast result
* if the given ray intersected the given body.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Returns false if the start position of the ray lies inside the given body.
* @param start the start point
* @param end the end point
* @param body the {@link Body} to test
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors whether or not to ignore sensor {@link BodyFixture}s
* @param result the raycast result
* @return boolean true if the {@link Ray} intersects the {@link Body}
* @throws NullPointerException if start, end, body, or result is null
* @see #raycast(Ray, Body, double, Filter, boolean, RaycastResult)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Vector2 start, Vector2 end, Body body, Filter filter, boolean ignoreSensors, RaycastResult result) {
// create the ray and obtain the maximum length
Vector2 d = start.to(end);
double maxLength = d.normalize();
Ray ray = new Ray(start, d);
// call the raycast method
return this.raycast(ray, body, maxLength, filter, ignoreSensors, result);
}
/**
* Performs a raycast against the given {@link Body} and returns true
* if the ray intersects the body.
*
* The given {@link RaycastResult} object, result, will be filled with the raycast result
* if the given ray intersected the given body.
*
* Pass 0 into the maxLength field to specify an infinite length {@link Ray}.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Returns false if the start position of the ray lies inside the given body.
* @param ray the {@link Ray} to cast
* @param body the {@link Body} to test
* @param maxLength the maximum length of the ray; 0 for infinite length
* @param ignoreSensors whether or not to ignore sensor {@link BodyFixture}s
* @param result the raycast result
* @return boolean true if the {@link Ray} intersects the {@link Body}
* @throws NullPointerException if ray, body, or result is null
* @see #raycast(Vector2, Vector2, Body, boolean, RaycastResult)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 2.0.0
*/
public boolean raycast(Ray ray, Body body, double maxLength, boolean ignoreSensors, RaycastResult result) {
return this.raycast(ray, body, maxLength, null, ignoreSensors, result);
}
/**
* Performs a raycast against the given {@link Body} and returns true
* if the ray intersects the body.
*
* The given {@link RaycastResult} object, result, will be filled with the raycast result
* if the given ray intersected the given body.
*
* Pass 0 into the maxLength field to specify an infinite length {@link Ray}.
*
* All raycasts pass through the {@link RaycastListener}s before being tested. If any
* {@link RaycastListener} doesn't allow the raycast then the body will not be tested.
*
* Returns false if the start position of the ray lies inside the given body.
* @param ray the {@link Ray} to cast
* @param body the {@link Body} to test
* @param maxLength the maximum length of the ray; 0 for infinite length
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors whether or not to ignore sensor {@link BodyFixture}s
* @param result the raycast result
* @return boolean true if the {@link Ray} intersects the {@link Body}
* @throws NullPointerException if ray, body, or result is null
* @see #raycast(Vector2, Vector2, Body, Filter, boolean, RaycastResult)
* @see RaycastListener#allow(Ray, Body, BodyFixture)
* @since 3.1.9
*/
public boolean raycast(Ray ray, Body body, double maxLength, Filter filter, boolean ignoreSensors, RaycastResult result) {
List listeners = this.getListeners(RaycastListener.class);
int rlSize = listeners.size();
boolean allow = true;
// get the number of fixtures
int size = body.getFixtureCount();
// get the body transform
Transform transform = body.getTransform();
// set the maximum length
double max = 0.0;
if (maxLength > 0.0) {
max = maxLength;
}
// create a raycast object to store the result
Raycast raycast = new Raycast();
// loop over the fixtures finding the closest one
boolean found = false;
for (int i = 0; i < size; i++) {
// get the fixture
BodyFixture fixture = body.getFixture(i);
// check for sensor
if (ignoreSensors && fixture.isSensor()) {
// skip this fixture
continue;
}
// check against the filter
if (filter != null && !filter.isAllowed(fixture.getFilter())) {
continue;
}
// notify the listeners to see if we should test this fixture
allow = true;
for (int j = 0; j < rlSize; j++) {
RaycastListener rl = listeners.get(j);
// see if we should test this fixture
if (!rl.allow(ray, body, fixture)) {
allow = false;
}
}
if (!allow) continue;
// get the convex shape
Convex convex = fixture.getShape();
// perform the raycast
if (this.raycastDetector.raycast(ray, max, convex, transform, raycast)) {
// notify the listeners to see if we should allow this result
allow = true;
for (int j = 0; j < rlSize; j++) {
RaycastListener rl = listeners.get(j);
// see if we should test this fixture
if (!rl.allow(ray, body, fixture, raycast)) {
allow = false;
}
}
if (!allow) continue;
// if the raycast detected a collision then set the new
// maximum distance
max = raycast.getDistance();
// assign the fixture
result.fixture = fixture;
// the last raycast will always be the minimum raycast
// flag that we did get a successful raycast
found = true;
}
}
// we only want to populate the
// result object if a result was found
if (found) {
result.body = body;
result.raycast = raycast;
}
return found;
}
/**
* Performs a linear convex cast on the world, placing any detected collisions into the given results list.
*
* This method does a static test of bodies (in other words, does not take into account the bodies linear
* or angular velocity, but rather assumes they are stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
*
* The {@link ConvexCastResult} class implements the Comparable interface to allow sorting by
* the time of impact.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, only the fixture that has the minimum time of impact will be added to the
* results list.
*
* Bodies in collision with the given convex at the beginning of the cast are not included in the results.
*
* Inactive bodies are ignored in this test.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param all true if all hits should be returned; false if only the first should be returned
* @param results the list to add the results to
* @return boolean true if a collision was found
* @since 3.1.5
* @see #convexCast(Convex, Transform, Vector2, double, boolean, boolean, boolean, List)
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, boolean ignoreSensors, boolean all, List results) {
return this.convexCast(convex, transform, deltaPosition, 0.0, null, ignoreSensors, true, all, results);
}
/**
* Performs a linear convex cast on the world, placing any detected collisions into the given results list.
*
* This method does a static test of bodies (in other words, does not take into account the bodies linear
* or angular velocity, but rather assumes they are stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
*
* The {@link ConvexCastResult} class implements the Comparable interface to allow sorting by
* the time of impact.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, only the fixture that has the minimum time of impact will be added to the
* results list.
*
* Bodies in collision with the given convex at the beginning of the cast are not included in the results.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param ignoreInactive true if inactive bodies should be ignored in the tests
* @param all true if all hits should be returned; false if only the first should be returned
* @param results the list to add the results to
* @return boolean true if a collision was found
* @since 3.1.9
* @see #convexCast(Convex, Transform, Vector2, double, boolean, boolean, boolean, List)
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
return this.convexCast(convex, transform, deltaPosition, 0.0, null, ignoreSensors, ignoreInactive, all, results);
}
/**
* Performs a linear convex cast on the world, placing any detected collisions into the given results list.
*
* This method does a static test of bodies (in other words, does not take into account the bodies linear
* or angular velocity, but rather assumes they are stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
* The deltaAngle
parameter is the change in angle over the linear cast and is interpolated linearly
* during detection.
*
* The {@link ConvexCastResult} class implements the Comparable interface to allow sorting by
* the time of impact.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, only the fixture that has the minimum time of impact will be added to the
* results list.
*
* Bodies in collision with the given convex at the beginning of the cast are not included in the results.
*
* Inactive bodies are ignored in this test.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param deltaAngle Δangle; the change in the angle; this is the change in the angle over the linear period
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param all true if all hits should be returned; false if only the first should be returned
* @param results the list to add the results to
* @return boolean true if a collision was found
* @see #convexCast(Convex, Transform, Vector2, double, boolean, boolean, boolean, List)
* @since 3.1.5
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, double deltaAngle, boolean ignoreSensors, boolean all, List results) {
return this.convexCast(convex, transform, deltaPosition, deltaAngle, null, ignoreSensors, true, all, results);
}
/**
* Performs a linear convex cast on the world, placing any detected collisions into the given results list.
*
* This method does a static test of bodies (in other words, does not take into account the bodies linear
* or angular velocity, but rather assumes they are stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
* The deltaAngle
parameter is the change in angle over the linear cast and is interpolated linearly
* during detection.
*
* The {@link ConvexCastResult} class implements the Comparable interface to allow sorting by
* the time of impact.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, only the fixture that has the minimum time of impact will be added to the
* results list.
*
* Bodies in collision with the given convex at the beginning of the cast are not included in the results.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param deltaAngle Δangle; the change in the angle; this is the change in the angle over the linear period
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param ignoreInactive true if inactive bodies should be ignored in the tests
* @param all true if all hits should be returned; false if only the first should be returned
* @param results the list to add the results to
* @return boolean true if a collision was found
* @since 3.1.9
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, double deltaAngle, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
return this.convexCast(convex, transform, deltaPosition, deltaAngle, null, ignoreSensors, ignoreInactive, all, results);
}
/**
* Performs a linear convex cast on the world, placing any detected collisions into the given results list.
*
* This method does a static test of bodies (in other words, does not take into account the bodies linear
* or angular velocity, but rather assumes they are stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
* The deltaAngle
parameter is the change in angle over the linear cast and is interpolated linearly
* during detection.
*
* The {@link ConvexCastResult} class implements the Comparable interface to allow sorting by
* the time of impact.
*
* If the all flag is false, the results list will only contain the closest result (if any).
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, only the fixture that has the minimum time of impact will be added to the
* results list.
*
* Bodies in collision with the given convex at the beginning of the cast are not included in the results.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param deltaAngle Δangle; the change in the angle; this is the change in the angle over the linear period
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param ignoreInactive true if inactive bodies should be ignored in the tests
* @param all true if all hits should be returned; false if only the first should be returned
* @param results the list to add the results to
* @return boolean true if a collision was found
* @since 3.1.9
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, double deltaAngle, Filter filter, boolean ignoreSensors, boolean ignoreInactive, boolean all, List results) {
// get the listeners
List listeners = this.getListeners(ConvexCastListener.class);
int clSize = listeners.size();
// compute a conservative AABB for the motion of the convex
double radius = convex.getRadius();
Vector2 startWorldCenter = transform.getTransformed(convex.getCenter());
AABB startAABB = new AABB(startWorldCenter, radius);
// linearlly interpolate to get the final transform given the
// change in position and angle
Transform finalTransform = transform.lerped(deltaPosition, deltaAngle, 1.0);
// get the end AABB
Vector2 endWorldCenter = finalTransform.getTransformed(convex.getCenter());
AABB endAABB = new AABB(endWorldCenter, radius);
// union the AABBs to get the swept AABB
AABB aabb = startAABB.getUnion(endAABB);
ConvexCastResult min = null;
final Vector2 dp2 = new Vector2();
double t2 = 1.0;
boolean found = false;
boolean allow = true;
AABBBroadphaseFilter bpFilter = new AABBBroadphaseFilter(ignoreInactive, ignoreSensors, filter);
// use the broadphase to filter first
List> items = this.broadphaseDetector.detect(aabb, bpFilter);
// loop over the potential collisions
for (CollisionItem item : items) {
Body body = item.getBody();
BodyFixture fixture = item.getFixture();
// only get the minimum fixture
double ft2 = t2;
// find the minimum time of impact for the given convex
// and the current body
TimeOfImpact bodyMinToi = null;
BodyFixture bodyMinFixture = null;
Transform bodyTransform = body.getTransform();
// notify the listeners to see if we should test this fixture
allow = true;
for (int j = 0; j < clSize; j++) {
ConvexCastListener ccl = listeners.get(j);
// see if we should test this fixture
if (!ccl.allow(convex, body, fixture)) {
allow = false;
}
}
if (!allow) continue;
// get the time of impact
Convex c = fixture.getShape();
TimeOfImpact timeOfImpact = new TimeOfImpact();
// we pass the zero vector and 0 for the change in position and angle for the body
// since we assume that it is not moving since this is a static test
if (this.timeOfImpactDetector.getTimeOfImpact(convex, transform, deltaPosition, deltaAngle, c, bodyTransform, dp2, 0.0, 0.0, ft2, timeOfImpact)) {
// notify the listeners to see if we should test this fixture
allow = true;
for (int j = 0; j < clSize; j++) {
ConvexCastListener ccl = listeners.get(j);
// see if we should test this fixture
if (!ccl.allow(convex, body, fixture, timeOfImpact)) {
allow = false;
}
}
if (!allow) continue;
// only save the minimum for the body
if (bodyMinToi == null || timeOfImpact.getTime() < bodyMinToi.getTime()) {
ft2 = timeOfImpact.getTime();
bodyMinToi = timeOfImpact;
bodyMinFixture = fixture;
}
}
if (bodyMinToi != null) {
if (!all) {
t2 = bodyMinToi.getTime();
if (min == null || bodyMinToi.getTime() < min.timeOfImpact.getTime()) {
min = new ConvexCastResult(body, bodyMinFixture, bodyMinToi);
}
} else {
ConvexCastResult result = new ConvexCastResult(body, fixture, timeOfImpact);
results.add(result);
}
found = true;
}
}
if (min != null) {
results.add(min);
}
// if something is in the list then we know we found a collision
return found;
}
/**
* Performs a linear convex cast on the given body, placing a detected collision into the given result object.
*
* This method does a static test of the body (in other words, does not take into account the body's linear
* or angular velocity, but rather assumes it is stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, the fixture that has the minimum time of impact will be the result.
*
* Returns false if the given body and convex are in collision at the beginning of the cast.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param body the body to cast against
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param result the convex cast result
* @return boolean true if a collision was found
* @since 3.1.5
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, Body body, boolean ignoreSensors, ConvexCastResult result) {
return this.convexCast(convex, transform, deltaPosition, 0, body, null, ignoreSensors, result);
}
/**
* Performs a linear convex cast on the given body, placing a detected collision into the given result object.
*
* This method does a static test of the body (in other words, does not take into account the body's linear
* or angular velocity, but rather assumes it is stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
* The deltaAngle
parameter is the change in angle over the linear cast and is interpolated linearly
* during detection.
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, the fixture that has the minimum time of impact will be the result.
*
* Returns false if the given body and convex are in collision at the beginning of the cast.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param deltaAngle Δangle; the change in the angle; this is the change in the angle over the linear period
* @param body the body to cast against
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param result the convex cast result
* @return boolean true if a collision was found
* @since 3.1.5
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, double deltaAngle, Body body, boolean ignoreSensors, ConvexCastResult result) {
return this.convexCast(convex, transform, deltaPosition, deltaAngle, body, null, ignoreSensors, result);
}
/**
* Performs a linear convex cast on the given body, placing a detected collision into the given result object.
*
* This method does a static test of the body (in other words, does not take into account the body's linear
* or angular velocity, but rather assumes it is stationary).
*
* The deltaPosition
parameter is the linear cast vector determining the direction and magnitude of the cast.
* The deltaAngle
parameter is the change in angle over the linear cast and is interpolated linearly
* during detection.
*
* All convex casts pass through the {@link ConvexCastListener}s before being tested. If any
* {@link ConvexCastListener} doesn't allow the convex cast, then the body will not be tested.
*
* For multi-fixtured bodies, the fixture that has the minimum time of impact will be the result.
*
* Returns false if the given body and convex are in collision at the beginning of the cast.
* @param convex the convex to cast
* @param transform the initial position and orientation of the convex
* @param deltaPosition Δposition; the change in position (the cast length and direction basically)
* @param deltaAngle Δangle; the change in the angle; this is the change in the angle over the linear period
* @param body the body to cast against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored in the tests
* @param result the convex cast result
* @return boolean true if a collision was found
* @since 3.1.9
*/
public boolean convexCast(Convex convex, Transform transform, Vector2 deltaPosition, double deltaAngle, Body body, Filter filter, boolean ignoreSensors, ConvexCastResult result) {
// get the listeners
List listeners = this.getListeners(ConvexCastListener.class);
int clSize = listeners.size();
boolean allow = true;
boolean found = false;
final Vector2 dp2 = new Vector2();
double t2 = 1.0;
// find the minimum time of impact for the given convex
// and the current body
int bSize = body.getFixtureCount();
Transform bodyTransform = body.getTransform();
// loop through all the body fixtures until we find
// a the fixture that has the smallest time of impact
for (int i = 0; i < bSize; i++) {
BodyFixture bodyFixture = body.getFixture(i);
// filter out sensors if desired
if (ignoreSensors && bodyFixture.isSensor()) continue;
// check the filter
if (filter != null && !filter.isAllowed(bodyFixture.getFilter())) continue;
allow = true;
for (int j = 0; j < clSize; j++) {
ConvexCastListener ccl = listeners.get(j);
// see if we should test this body
if (!ccl.allow(convex, body, bodyFixture)) {
allow = false;
}
}
if (!allow) return false;
// get the time of impact
Convex c = bodyFixture.getShape();
TimeOfImpact toi = new TimeOfImpact();
// we pass the zero vector and 0 for the change in position and angle for the body
// since we assume that it is not moving since this is a static test
if (this.timeOfImpactDetector.getTimeOfImpact(convex, transform, deltaPosition, deltaAngle, c, bodyTransform, dp2, 0.0, 0.0, t2, toi)) {
// notify the listeners to see if we should test this fixture
allow = true;
for (int j = 0; j < clSize; j++) {
ConvexCastListener ccl = listeners.get(j);
// see if we should test this fixture
if (!ccl.allow(convex, body, bodyFixture, toi)) {
allow = false;
}
}
if (!allow) continue;
// set the new maximum time
t2 = toi.getTime();
// save the min time of impact
result.fixture = bodyFixture;
result.timeOfImpact = toi;
result.body = body;
// set the found flag
found = true;
}
}
return found;
}
/**
* Returns true if the given AABB overlaps a {@link Body} in this {@link World}.
*
* If any part of a body is overlaping the AABB, the body is added to the list.
*
* This performs a static collision test of the world using the {@link BroadphaseDetector}.
*
* This may return bodies who only have sensor fixtures overlapping.
*
* Inactive bodies are ignored in this test.
* @param aabb the world space {@link AABB}
* @param results the list of overlapping bodies and fixtures
* @return boolean true if the AABB overlaps any body
* @since 3.1.9
*/
public boolean detect(AABB aabb, List results) {
return this.detect(aabb, null, false, true, results);
}
/**
* Returns true if the given AABB overlaps a {@link Body} {@link Fixture} in this {@link World}.
*
* If any part of a body is overlaping the AABB, the body and that respective fixture is added
* to the returned list.
*
* This performs a static collision test of the world using the {@link BroadphaseDetector}.
*
* This may return bodies who only have sensor fixtures overlapping.
* @param aabb the world space {@link AABB}
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if the AABB overlaps any body
* @since 3.1.9
*/
public boolean detect(AABB aabb, boolean ignoreInactive, List results) {
return this.detect(aabb, null, false, ignoreInactive, results);
}
/**
* Returns true if the given AABB overlaps a {@link Body} in this {@link World}.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
* @param aabb the world space {@link AABB}
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if the AABB overlaps any fixture
* @since 3.1.9
*/
public boolean detect(AABB aabb, boolean ignoreSensors, boolean ignoreInactive, List results) {
return this.detect(aabb, null, ignoreSensors, ignoreInactive, results);
}
/**
* Returns true if the given AABB overlaps a {@link Body} in this {@link World}.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
* @param aabb the world space {@link AABB}
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if the AABB overlaps any fixture
* @since 3.1.9
*/
public boolean detect(AABB aabb, Filter filter, boolean ignoreSensors, boolean ignoreInactive, List results) {
List listeners = this.getListeners(DetectListener.class);
int dlSize = listeners.size();
AABBBroadphaseFilter bpFilter = new AABBBroadphaseFilter(ignoreInactive, ignoreSensors, filter);
List> collisions = this.broadphaseDetector.detect(aabb, bpFilter);
boolean found = false;
int bSize = collisions.size();
boolean allow;
for (int i = 0; i < bSize; i++) {
CollisionItem item = collisions.get(i);
Body body = item.getBody();
BodyFixture fixture = item.getFixture();
// check body's fixtures next
Transform transform = body.getTransform();
// pass through the listeners
allow = true;
for (int j = 0; j < dlSize; j++) {
DetectListener dl = listeners.get(j);
if (!dl.allow(aabb, body, fixture)) {
allow = false;
}
}
if (!allow) {
continue;
}
// create an AABB for the fixture
AABB faabb = fixture.getShape().createAABB(transform);
// test the aabbs
if (aabb.overlaps(faabb)) {
// add this fixture to the results list
DetectResult result = new DetectResult(body, fixture);
results.add(result);
found = true;
}
}
return found;
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The returned results may include sensor fixutres.
*
* Inactive bodies are ignored in this test.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in world coordinates
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, boolean, List)
*/
public boolean detect(Convex convex, List results) {
return this.detect(convex, IDENTITY, null, false, true, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Inactive bodies are ignored in this test.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in world coordinates
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, boolean, boolean, List)
*/
public boolean detect(Convex convex, boolean ignoreSensors, List results) {
return this.detect(convex, IDENTITY, null, ignoreSensors, true, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in world coordinates
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, Filter, boolean, boolean, List)
*/
public boolean detect(Convex convex, boolean ignoreSensors, boolean ignoreInactive, List results) {
return this.detect(convex, IDENTITY, null, ignoreSensors, ignoreInactive, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in world coordinates
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, Filter, boolean, boolean, boolean, List)
*/
public boolean detect(Convex convex, Filter filter, boolean ignoreSensors, boolean ignoreInactive, List results) {
return this.detect(convex, IDENTITY, filter, ignoreSensors, ignoreInactive, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Use the includeCollisionData
parameter to have the {@link Penetration} object
* filled in the {@link DetectResult}s. Including this information will have a performance impact.
* @param convex the convex shape in world coordinates
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param includeCollisionData true if the overlap {@link Penetration} should be returned
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Filter filter, boolean ignoreSensors, boolean ignoreInactive, boolean includeCollisionData, List results) {
return this.detect(convex, IDENTITY, filter, ignoreSensors, ignoreInactive, includeCollisionData, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The returned results may include sensor fixutres.
*
* Inactive bodies are ignored in this test.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in local coordinates
* @param transform the convex shape's world transform
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, boolean, List)
*/
public boolean detect(Convex convex, Transform transform, List results) {
return this.detect(convex, transform, null, false, true, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Inactive bodies are ignored in this test.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in local coordinates
* @param transform the convex shape's world transform
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, boolean, boolean, List)
*/
public boolean detect(Convex convex, Transform transform, boolean ignoreSensors, List results) {
return this.detect(convex, transform, null, ignoreSensors, true, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in local coordinates
* @param transform the convex shape's world transform
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, Filter, boolean, boolean, List)
*/
public boolean detect(Convex convex, Transform transform, boolean ignoreSensors, boolean ignoreInactive, List results) {
return this.detect(convex, transform, null, ignoreSensors, ignoreInactive, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the convex shape in local coordinates
* @param transform the convex shape's world transform
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
* @see #detect(Convex, Transform, Filter, boolean, boolean, boolean, List)
*/
public boolean detect(Convex convex, Transform transform, Filter filter, boolean ignoreSensors, boolean ignoreInactive, List results) {
return this.detect(convex, transform, filter, ignoreSensors, ignoreInactive, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps a body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Use the includeCollisionData
parameter to have the {@link Penetration} object
* filled in the {@link DetectResult}s. Including this information will have a performance impact.
* @param convex the convex shape in local coordinates
* @param transform the convex shape's world transform
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param ignoreInactive true if inactive bodies should be ignored
* @param includeCollisionData true if the overlap {@link Penetration} should be returned
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Transform transform, Filter filter, boolean ignoreSensors, boolean ignoreInactive, boolean includeCollisionData, List results) {
List listeners = this.getListeners(DetectListener.class);
int dlSize = listeners.size();
boolean allow = true;
// create an aabb for the given convex
AABB aabb = convex.createAABB(transform);
AABBBroadphaseFilter bpFilter = new AABBBroadphaseFilter(ignoreInactive, ignoreSensors, filter);
// test using the broadphase to rule out as many bodies as we can
List> items = this.broadphaseDetector.detect(aabb, bpFilter);
// now perform a more accurate test
int bSize = items.size();
boolean found = false;
for (int i = 0; i < bSize; i++) {
CollisionItem item = items.get(i);
Body body = item.getBody();
BodyFixture fixture = item.getFixture();
// get the body transform
Transform bt = body.getTransform();
// pass through the listeners
allow = true;
for (int j = 0; j < dlSize; j++) {
DetectListener dl = listeners.get(j);
if (!dl.allow(convex, transform, body, fixture)) {
allow = false;
}
}
if (!allow) {
continue;
}
// just perform a boolean test since its typically faster
Convex bc = fixture.getShape();
boolean collision = false;
// should we use the fast method or the one that returns the collision info
Penetration penetration = (includeCollisionData ? new Penetration() : null);
if (includeCollisionData) {
collision = this.narrowphaseDetector.detect(convex, transform, bc, bt, penetration);
} else {
collision = this.narrowphaseDetector.detect(convex, transform, bc, bt);
}
if (collision) {
// add this fixture to the results list
DetectResult result = new DetectResult(body, fixture, penetration);
results.add(result);
found = true;
}
}
// return the bodies in collision
return found;
}
/**
* Returns true if the given {@link AABB} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the AABB overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
* @param aabb the {@link AABB} in world coordinates
* @param body the {@link Body} to test against
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(AABB aabb, Body body, boolean ignoreSensors, List results) {
return this.detect(aabb, body, null, ignoreSensors, results);
}
/**
* Returns true if the given {@link AABB} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the AABB overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
* @param aabb the {@link AABB} in world coordinates
* @param body the {@link Body} to test against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(AABB aabb, Body body, Filter filter, boolean ignoreSensors, List results) {
List listeners = this.getListeners(DetectListener.class);
int dlSize = listeners.size();
boolean allow = true;
// test the AABBs
boolean found = false;
AABB baabb = this.broadphaseDetector.getAABB(body);
if (baabb == null) {
baabb = body.createAABB();
}
if (aabb.overlaps(baabb)) {
// check body's fixtures next
Transform transform = body.getTransform();
int fSize = body.getFixtureCount();
for (int j = 0; j < fSize; j++) {
BodyFixture fixture = body.getFixture(j);
// test for sensors
if (ignoreSensors && fixture.isSensor()) continue;
// test the filter
if (filter != null && !filter.isAllowed(fixture.getFilter())) continue;
// pass through the listeners
allow = true;
for (int k = 0; k < dlSize; k++) {
DetectListener dl = listeners.get(k);
if (!dl.allow(aabb, body, fixture)) {
allow = false;
}
}
if (!allow) {
continue;
}
// create an AABB for the fixture
AABB faabb = fixture.getShape().createAABB(transform);
// test the aabbs
if (aabb.overlaps(faabb)) {
// add this fixture to the results list
DetectResult result = new DetectResult(body, fixture);
results.add(result);
found = true;
}
}
}
return found;
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the {@link Convex} in world coordinates
* @param body the {@link Body} to test against
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Body body, boolean ignoreSensors, List results) {
return this.detect(convex, IDENTITY, body, null, ignoreSensors, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the {@link Convex} in world coordinates
* @param body the {@link Body} to test against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Body body, Filter filter, boolean ignoreSensors, List results) {
return this.detect(convex, IDENTITY, body, filter, ignoreSensors, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Use the includeCollisionData
parameter to have the {@link Penetration} object
* filled in the {@link DetectResult}s. Including this information negatively impacts performance.
* @param convex the {@link Convex} in world coordinates
* @param body the {@link Body} to test against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param includeCollisionData true if the overlap {@link Penetration} should be returned
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Body body, Filter filter, boolean ignoreSensors, boolean includeCollisionData, List results) {
return this.detect(convex, IDENTITY, body, filter, ignoreSensors, includeCollisionData, results);
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the {@link Convex} in local coordinates
* @param transform the convex shape's world {@link Transform}
* @param body the {@link Body} to test against
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Transform transform, Body body, boolean ignoreSensors, List results) {
return this.detect(convex, transform, body, null, ignoreSensors, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* The results from this test will not include {@link Penetration} objects.
* @param convex the {@link Convex} in local coordinates
* @param transform the convex shape's world {@link Transform}
* @param body the {@link Body} to test against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Transform transform, Body body, Filter filter, boolean ignoreSensors, List results) {
return this.detect(convex, transform, body, filter, ignoreSensors, false, results);
}
/**
* Returns true if the given {@link Convex} overlaps the given body in the world.
*
* If this method returns true, the results list will contain the bodies and
* fixtures that the convex overlaps.
*
* Use the {@link Body#isInContact(Body)} method instead if you want to test if two bodies
* are colliding.
*
* Use the includeCollisionData
parameter to have the {@link Penetration} object
* filled in the {@link DetectResult}s. Including this information negatively impacts performance.
* @param convex the {@link Convex} in local coordinates
* @param transform the convex shape's world {@link Transform}
* @param body the {@link Body} to test against
* @param filter the {@link Filter} to use against the fixtures; can be null
* @param includeCollisionData true if the overlap {@link Penetration} should be returned
* @param ignoreSensors true if sensor fixtures should be ignored
* @param results the list of overlapping bodies and fixtures
* @return boolean true if an overlap was found
* @since 3.1.9
*/
public boolean detect(Convex convex, Transform transform, Body body, Filter filter, boolean ignoreSensors, boolean includeCollisionData, List results) {
List listeners = this.getListeners(DetectListener.class);
int dlSize = listeners.size();
// make sure we can test the body
boolean allow = true;
for (int i = 0; i < dlSize; i++) {
DetectListener dl = listeners.get(i);
if (!dl.allow(convex, transform, body)) {
allow = false;
}
}
if (!allow) return false;
// create an aabb for the given convex
AABB aabb = convex.createAABB(transform);
// test using the broadphase to rule out as many bodies as we can
AABB baabb = this.broadphaseDetector.getAABB(body);
if (baabb == null) {
baabb = body.createAABB();
}
// now perform an AABB test first
boolean found = false;
if (aabb.overlaps(baabb)) {
// get the body transform
Transform bt = body.getTransform();
// test all the fixtures
int fSize = body.getFixtureCount();
for (int i = 0; i < fSize; i++) {
BodyFixture fixture = body.getFixture(i);
// check against the sensor flag
if (ignoreSensors && fixture.isSensor()) continue;
// check against the filter if given
Filter ff = fixture.getFilter();
if (filter != null && !ff.isAllowed(filter)) continue;
// pass through the listeners
allow = true;
for (int j = 0; j < dlSize; j++) {
DetectListener dl = listeners.get(j);
if (!dl.allow(convex, transform, body, fixture)) {
allow = false;
}
}
if (!allow) {
continue;
}
// just perform a boolean test since its typically faster
Convex bc = fixture.getShape();
boolean collision = false;
// should we use the fast method or the one that returns the collision info
Penetration penetration = (includeCollisionData ? new Penetration() : null);
if (includeCollisionData) {
collision = this.narrowphaseDetector.detect(convex, transform, bc, bt, penetration);
} else {
collision = this.narrowphaseDetector.detect(convex, transform, bc, bt);
}
if (collision) {
// add this fixture to the results list
DetectResult result = new DetectResult(body, fixture, penetration);
results.add(result);
found = true;
}
}
}
// return the bodies in collision
return found;
}
/**
* Shifts the coordinates of the entire world by the given amount.
*
* NewPosition = OldPosition + shift
*
* This method is useful in situations where the world is very large
* causing very large numbers to be used in the computations. Shifting
* the coordinate system allows the computations to be localized and
* retain accuracy.
*
* This method modifies the coordinates of every body and joint in the world.
*
* Adding joints or bodies after this method is called should consider that
* everything has been shifted.
*
* This method does NOT require a call to {@link #setUpdateRequired(boolean)}.
* @param shift the distance to shift along the x and y axes
* @since 3.2.0
*/
public void shift(Vector2 shift) {
// update the bodies
int bSize = this.bodies.size();
for (int i = 0; i < bSize; i++) {
Body body = this.bodies.get(i);
body.shift(shift);
}
// update the joints
int jSize = this.joints.size();
for (int i = 0; i < jSize; i++) {
Joint joint = this.joints.get(i);
joint.shift(shift);
}
// update the broadphase
this.broadphaseDetector.shift(shift);
// update the bounds
if (this.bounds != null) {
this.bounds.shift(shift);
}
// update contact manager
this.contactManager.shift(shift);
}
/**
* Adds the given {@link Body} to the {@link World}.
* @param body the {@link Body} to add
* @throws NullPointerException if body is null
* @throws IllegalArgumentException if body has already been added to this world or if its a member of another world instance
* @since 3.1.1
*/
public void addBody(Body body) {
// check for null body
if (body == null) throw new NullPointerException(Messages.getString("dynamics.world.addNullBody"));
// dont allow adding it twice
if (body.getOwner() == this) throw new IllegalArgumentException(Messages.getString("dynamics.world.addExistingBody"));
// dont allow a body that already is assigned to another world
if (body.getOwner() != null) throw new IllegalArgumentException(Messages.getString("dynamics.world.addOtherWorldBody"));
// add it to the world
this.bodies.add(body);
// set the world property on the body
body.setFixtureModificationHandler(new BodyModificationHandler(body));
body.setOwner(this);
// add it to the broadphase
this.broadphaseDetector.add(body);
}
/**
* Adds the given {@link Joint} to the {@link World}.
* @param joint the {@link Joint} to add
* @throws NullPointerException if joint is null
* @throws IllegalArgumentException if joint has already been added to this world or if its a member of another world instance
* @since 3.1.1
*/
public void addJoint(Joint joint) {
// check for null joint
if (joint == null) throw new NullPointerException(Messages.getString("dynamics.world.addNullJoint"));
// dont allow adding it twice
if (joint.getOwner() == this) throw new IllegalArgumentException(Messages.getString("dynamics.world.addExistingBody"));
// dont allow a joint that already is assigned to another world
if (joint.getOwner() != null) throw new IllegalArgumentException(Messages.getString("dynamics.world.addOtherWorldBody"));
// add the joint to the joint list
this.joints.add(joint);
joint.setOwner(this);
// get the associated bodies
Body body1 = (Body)joint.getBody1();
Body body2 = (Body)joint.getBody2();
// create a joint edge from the first body to the second
JointEdge jointEdge1 = new JointEdge(body2, joint);
// add the edge to the body
body1.joints.add(jointEdge1);
// create a joint edge from the second body to the first
JointEdge jointEdge2 = new JointEdge(body1, joint);
// add the edge to the body
body2.joints.add(jointEdge2);
}
/**
* Returns true if this world contains the given body.
* @param body the {@link Body} to test for
* @return boolean true if the body is contained in this world
* @since 3.1.1
*/
public boolean containsBody(Body body) {
return this.bodies.contains(body);
}
/**
* Returns true if this world contains the given joint.
* @param joint the {@link Joint} to test for
* @return boolean true if the joint is contained in this world
* @since 3.1.1
*/
public boolean containsJoint(Joint joint) {
return this.joints.contains(joint);
}
/**
* Removes the {@link Body} at the given index from this {@link World}.
*
* Use the {@link #removeBody(int, boolean)} method to enable implicit
* destruction notification.
* @param index the index of the body to remove.
* @return boolean true if the body was removed
* @since 3.2.0
*/
public boolean removeBody(int index) {
return removeBody(index, false);
}
/**
* Removes the {@link Body} at the given index from this {@link World}.
*
* When a body is removed, joints and contacts may be implicitly destroyed.
* Pass true to the notify parameter to be notified of the destruction of these objects
* via the {@link DestructionListener}s.
*
* This method does not trigger {@link ContactListener#end(ContactPoint)} events
* for the contacts that are being removed.
* @param index the index of the body to remove.
* @param notify true if implicit destruction should be notified
* @return boolean true if the body was removed
* @since 3.2.0
*/
public boolean removeBody(int index, boolean notify) {
Body body = this.bodies.get(index);
return removeBody(body, notify);
}
/**
* Removes the given {@link Body} from this {@link World}.
*
* Use the {@link #removeBody(Body, boolean)} method to enable implicit
* destruction notification.
* @param body the {@link Body} to remove.
* @return boolean true if the body was removed
*/
public boolean removeBody(Body body) {
return removeBody(body, false);
}
/**
* Removes the given {@link Body} from this {@link World}.
*
* When a body is removed, joints and contacts may be implicitly destroyed.
* Pass true to the notify parameter to be notified of the destruction of these objects
* via the {@link DestructionListener}s.
*
* This method does not trigger {@link ContactListener#end(ContactPoint)} events
* for the contacts that are being removed.
* @param body the {@link Body} to remove
* @param notify true if implicit destruction should be notified
* @return boolean true if the body was removed
* @since 3.1.1
*/
public boolean removeBody(Body body, boolean notify) {
List listeners = null;
if (notify) {
listeners = this.getListeners(DestructionListener.class);
}
// check for null body
if (body == null) return false;
// remove the body from the list
boolean removed = this.bodies.remove(body);
// only remove joints and contacts if the body was removed
if (removed) {
// set the world property to null
body.setFixtureModificationHandler(null);
body.setOwner(null);
// remove the body from the broadphase
this.broadphaseDetector.remove(body);
// wake up any bodies connected to this body by a joint
// and destroy the joints and remove the edges
Iterator aIterator = body.joints.iterator();
while (aIterator.hasNext()) {
// get the joint edge
JointEdge jointEdge = aIterator.next();
// remove the joint edge from the given body
aIterator.remove();
// get the joint
Joint joint = jointEdge.interaction;
joint.setOwner(null);
// get the other body
Body other = jointEdge.other;
// wake up the other body
other.setAsleep(false);
// remove the joint edge from the other body
Iterator bIterator = other.joints.iterator();
while (bIterator.hasNext()) {
// get the joint edge
JointEdge otherJointEdge = bIterator.next();
// get the joint
Joint otherJoint = otherJointEdge.interaction;
// are the joints the same object reference
if (otherJoint == joint) {
// remove the joint edge
bIterator.remove();
// we can break from the loop since there should
// not be more than one joint edge per joint per body
break;
}
}
// notify of the destroyed joint
if (notify) {
for (DestructionListener dl : listeners) {
dl.destroyed(joint);
}
}
// remove the joint from the world
this.joints.remove(joint);
}
// remove any contacts this body had with any other body
Iterator acIterator = body.contacts.iterator();
while (acIterator.hasNext()) {
// get the contact edge
ContactEdge contactEdge = acIterator.next();
// remove the contact edge from the given body
acIterator.remove();
// get the contact constraint
ContactConstraint contactConstraint = contactEdge.interaction;
// get the other body
Body other = contactEdge.other;
// wake up the other body
other.setAsleep(false);
// remove the contact edge connected from the other body
// to this body
Iterator iterator = other.contacts.iterator();
while (iterator.hasNext()) {
ContactEdge otherContactEdge = iterator.next();
// get the contact constraint
ContactConstraint otherContactConstraint = otherContactEdge.interaction;
// check if the contact constraint is the same reference
if (otherContactConstraint == contactConstraint) {
// remove the contact edge
iterator.remove();
// break from the loop since there should only be
// one contact edge per body pair
break;
}
}
// remove the contact constraint from the contact manager
this.contactManager.end(contactConstraint);
// loop over the contact points
List contacts = contactConstraint.getContacts();
int size = contacts.size();
for (int j = 0; j < size; j++) {
// get the contact
Contact contact = contacts.get(j);
// create a contact point for notification
ContactPoint contactPoint = new ContactPoint(contactConstraint, contact);
// call the destruction listeners
if (notify) {
for (DestructionListener dl : listeners) {
dl.destroyed(contactPoint);
}
}
}
}
}
return removed;
}
/**
* Removes the {@link Joint} at the given index from this {@link World}.
*
* No other objects are implicitly destroyed with joints are removed.
* @param index the index of the {@link Joint} to remove
* @return boolean true if the {@link Joint} was removed
* @since 3.2.0
*/
public boolean removeJoint(int index) {
Joint joint = this.joints.get(index);
return removeJoint(joint);
}
/**
* Removes the given {@link Joint} from this {@link World}.
*
* No other objects are implicitly destroyed with joints are removed.
* @param joint the {@link Joint} to remove
* @return boolean true if the {@link Joint} was removed
*/
public boolean removeJoint(Joint joint) {
// check for null joint
if (joint == null) return false;
// remove the joint from the joint list
boolean removed = this.joints.remove(joint);
// see if the given joint was removed
if (removed) {
// set the world property to null
joint.setOwner(null);
// get the involved bodies
Body body1 = (Body)joint.getBody1();
Body body2 = (Body)joint.getBody2();
// remove the joint edges from body1
Iterator iterator = body1.joints.iterator();
while (iterator.hasNext()) {
// see if this is the edge we want to remove
JointEdge jointEdge = iterator.next();
if (jointEdge.interaction == joint) {
// then remove this joint edge
iterator.remove();
// joints should only have one joint edge
// per body
break;
}
}
// remove the joint edges from body2
iterator = body2.joints.iterator();
while (iterator.hasNext()) {
// see if this is the edge we want to remove
JointEdge jointEdge = iterator.next();
if (jointEdge.interaction == joint) {
// then remove this joint edge
iterator.remove();
// joints should only have one joint edge
// per body
break;
}
}
// finally wake both bodies
body1.setAsleep(false);
body2.setAsleep(false);
}
return removed;
}
/**
* Removes all the joints and bodies from this world.
*
* This method does not notify of destroyed objects.
* @see #removeAllBodiesAndJoints(boolean)
* @since 3.1.1
*/
public void removeAllBodiesAndJoints() {
this.removeAllBodiesAndJoints(false);
}
/**
* Removes all the joints and bodies from this world.
* @param notify true if destruction of joints and contacts should be notified of by the {@link DestructionListener}
* @since 3.1.1
*/
public void removeAllBodiesAndJoints(boolean notify) {
List listeners = null;
if (notify) {
listeners = this.getListeners(DestructionListener.class);
}
// loop over the bodies and clear the
// joints and contacts
int bsize = this.bodies.size();
for (int i = 0; i < bsize; i++) {
// get the body
Body body = this.bodies.get(i);
// clear the joint edges
body.joints.clear();
// do we need to notify?
if (notify) {
// notify of all the destroyed contacts
Iterator aIterator = body.contacts.iterator();
while (aIterator.hasNext()) {
// get the contact edge
ContactEdge contactEdge = aIterator.next();
// get the other body involved
Body other = contactEdge.other;
// get the contact constraint
ContactConstraint contactConstraint = contactEdge.interaction;
// find the other contact edge
Iterator bIterator = other.contacts.iterator();
while (bIterator.hasNext()) {
// get the contact edge
ContactEdge otherContactEdge = bIterator.next();
// get the contact constraint on the edge
ContactConstraint otherContactConstraint = otherContactEdge.interaction;
// are the constraints the same object reference
if (otherContactConstraint == contactConstraint) {
// if so then remove it
bIterator.remove();
// there should only be one contact edge
// for each body-body pair
break;
}
}
// notify of all the contacts on the contact constraint
List contacts = contactConstraint.getContacts();
int csize = contacts.size();
for (int j = 0; j < csize; j++) {
Contact contact = contacts.get(j);
// create a contact point for notification
ContactPoint contactPoint = new ContactPoint(contactConstraint, contact);
// call the destruction listeners
for (DestructionListener dl : listeners) {
dl.destroyed(contactPoint);
}
}
}
// notify of the destroyed body
for (DestructionListener dl : listeners) {
dl.destroyed(body);
}
}
// clear all the contacts
body.contacts.clear();
// set the world to null
body.setFixtureModificationHandler(null);
body.setOwner(null);
}
// do we need to notify?
if (notify) {
// notify of all the destroyed joints
int jsize = this.joints.size();
for (int i = 0; i < jsize; i++) {
// get the joint
Joint joint = this.joints.get(i);
joint.setOwner(null);
// call the destruction listeners
for (DestructionListener dl : listeners) {
dl.destroyed(joint);
}
}
}
// clear all the broadphase bodies
this.broadphaseDetector.clear();
// clear all the joints
this.joints.clear();
// clear all the bodies
this.bodies.clear();
// clear the contact manager of cached contacts
this.contactManager.clear();
}
/**
* This is a convenience method for the {@link #removeAllBodiesAndJoints()} method since all joints will be removed
* when all bodies are removed anyway.
*
* This method does not notify of the destroyed contacts, joints, etc.
* @see #removeAllBodies(boolean)
* @since 3.0.1
*/
public void removeAllBodies() {
this.removeAllBodiesAndJoints(false);
}
/**
* This is a convenience method for the {@link #removeAllBodiesAndJoints(boolean)} method since all joints will be removed
* when all bodies are removed anyway.
* @param notify true if destruction of joints and contacts should be notified of by the {@link DestructionListener}
* @since 3.0.1
*/
public void removeAllBodies(boolean notify) {
this.removeAllBodiesAndJoints(notify);
}
/**
* Removes all {@link Joint}s from this {@link World}.
*
* This method does not notify of the joints removed.
* @see #removeAllJoints(boolean)
* @since 3.0.1
*/
public void removeAllJoints() {
this.removeAllJoints(false);
}
/**
* Removes all {@link Joint}s from this {@link World}.
* @param notify true if destruction of joints should be notified of by the {@link DestructionListener}
* @since 3.0.1
*/
public void removeAllJoints(boolean notify) {
List listeners = null;
if (notify) {
listeners = this.getListeners(DestructionListener.class);
}
// get the number of joints
int jSize = this.joints.size();
// remove all the joints
for (int i = 0; i < jSize; i++) {
// remove the joint from the joint list
Joint joint = this.joints.get(i);
joint.setOwner(null);
// get the involved bodies
Body body1 = (Body)joint.getBody1();
Body body2 = (Body)joint.getBody2();
// remove the joint edges from body1
Iterator iterator = body1.joints.iterator();
while (iterator.hasNext()) {
// see if this is the edge we want to remove
JointEdge jointEdge = iterator.next();
if (jointEdge.interaction == joint) {
// then remove this joint edge
iterator.remove();
// joints should only have one joint edge
// per body
break;
}
}
// remove the joint edges from body2
iterator = body2.joints.iterator();
while (iterator.hasNext()) {
// see if this is the edge we want to remove
JointEdge jointEdge = iterator.next();
if (jointEdge.interaction == joint) {
// then remove this joint edge
iterator.remove();
// joints should only have one joint edge
// per body
break;
}
}
// finally wake both bodies
body1.setAsleep(false);
body2.setAsleep(false);
// notify of the destruction if required
if (notify) {
for (DestructionListener dl : listeners) {
dl.destroyed(joint);
}
}
}
// remove all the joints from the joint list
this.joints.clear();
}
/**
* Returns true if upon the next time step the contacts must be updated.
*
* This is typically set via user code when something about the simulation changes
* that can affect collision detection.
* @return boolean
* @see #setUpdateRequired(boolean)
*/
public boolean isUpdateRequired() {
return this.updateRequired;
}
/**
* Sets the update required flag.
*
* Set this flag to true if any of the following conditions have been met:
*
* - If a Body has been added or removed from the World
* - If a Body has been translated or rotated
* - If a Body's state has been manually changed via the Body.setActive(boolean) method
* - If a BodyFixture has been added or removed from a Body
* - If a BodyFixture's sensor flag has been manually changed via the BodyFixture.setSensor(boolean) method
* - If a BodyFixture's filter has been manually changed via the BodyFixture.setFilter(boolean) method
* - If a BodyFixture's restitution or friction coefficient has changed
* - If a BodyFixture's Shape has been translated or rotated
* - If a BodyFixture's Shape has been changed (vertices, radius, etc.)
* - If a Body's type has changed to or from Static (this is caused by the using setMassType(Mass.INFINITE/Mass.NORMAL) method)
* - If a Joint has been added or removed from the World in which the joined bodies should not be allowed to collide
* - If the World's CoefficientMixer has been changed
*
* @param flag the flag
*/
public void setUpdateRequired(boolean flag) {
this.updateRequired = flag;
}
/**
* Returns the settings for this world.
* @return {@link Settings}
* @since 3.0.3
*/
public Settings getSettings() {
return this.settings;
}
/**
* Sets the dynamics settings for this world.
* @param settings the desired settings
* @throws NullPointerException if the given settings is null
* @since 3.0.3
*/
public void setSettings(Settings settings) {
if (settings == null) throw new NullPointerException(Messages.getString("dynamics.world.nullSettings"));
this.settings = settings;
}
/**
* Sets the acceleration due to gravity.
* @param gravity the gravity in meters/second2
* @throws NullPointerException if gravity is null
*/
public void setGravity(Vector2 gravity) {
if (gravity == null) throw new NullPointerException(Messages.getString("dynamics.world.nullGravity"));
this.gravity = gravity;
}
/**
* Returns the acceleration due to gravity.
* @return {@link Vector2} the gravity in meters/second2
*/
public Vector2 getGravity() {
return this.gravity;
}
/**
* Sets the bounds of this {@link World}.
* @param bounds the bounds; can be null
*/
public void setBounds(Bounds bounds) {
this.bounds = bounds;
}
/**
* Returns the bounds of this world.
*
* This will return null if no bounds were initially set
* or if it was set to null via the {@link #setBounds(Bounds)}
* method.
* @return {@link Bounds} the bounds or null
*/
public Bounds getBounds() {
return this.bounds;
}
/**
* Returns the listeners that are of the given type (or sub types)
* of the given type.
*
* Returns an empty list if no listeners for the given type are found.
*
* Returns null if clazz is null.
*
* Example usage:
*
* world.getListeners(ContactListener.class);
*
* @param the listener type
* @param clazz the type of listener to get
* @return List<T>
* @since 3.1.0
*/
public List getListeners(Class clazz) {
// check for null
if (clazz == null) return null;
// create a new list and loop over the listeners
List listeners = new ArrayList();
getListeners(clazz, listeners);
// return the new list
return listeners;
}
/**
* Returns the listeners of the given type (or sub types) in the given list.
*
* This method does not clear the given listeners list before
* adding the listeners.
*
* If clazz or listeners is null, this method immediately returns.
*
* Example usage:
*
* List<ContactListener> list = ...;
* world.getListeners(ContactListener.class, list);
*
* @param the listener type
* @param clazz the type of listener to get
* @param listeners the list to add the listeners to
* @since 3.1.1
*/
public void getListeners(Class clazz, List listeners) {
// check for null
if (clazz == null || listeners == null) return;
// create a new list and loop over the listeners
int lSize = this.listeners.size();
for (int i = 0; i < lSize; i++) {
Listener listener = this.listeners.get(i);
// check if the listener is of the given type
if (clazz.isInstance(listener)) {
// if so, add it to the new list
listeners.add(clazz.cast(listener));
}
}
}
/**
* Adds the given listener to the list of listeners.
* @param listener the listener
* @throws NullPointerException if the given listener is null
* @throws IllegalArgumentException if the given listener has already been added to this world
* @since 3.1.0
*/
public void addListener(Listener listener) {
// make sure its not null
if (listener == null) throw new NullPointerException(Messages.getString("dynamics.world.nullListener"));
// make sure its not already been added
if (this.listeners.contains(listener)) throw new IllegalArgumentException("dynamics.world.addExistingListener");
// then add the listener
this.listeners.add(listener);
}
/**
* Returns true if the given listener is already attached to this world.
* @param listener the listener
* @return boolean
* @since 3.1.1
*/
public boolean containsListener(Listener listener) {
return this.listeners.contains(listener);
}
/**
* Removes the given listener from this world.
* @param listener the listener to remove
* @return boolean true if the listener was removed
* @since 3.1.0
*/
public boolean removeListener(Listener listener) {
return this.listeners.remove(listener);
}
/**
* Removes all the listeners.
* @return int the number of listeners removed
* @since 3.1.1
*/
public int removeAllListeners() {
int count = this.listeners.size();
this.listeners.clear();
return count;
}
/**
* Removes all the listeners of the specified type (or sub types).
*
* Returns zero if the given type is null or there are zero listeners
* attached.
*
* Example usage:
*
* world.removeAllListeners(ContactListener.class);
*
* @param the listener type
* @param clazz the listener type
* @return int the number of listeners removed
* @since 3.1.1
*/
public int removeAllListeners(Class clazz) {
// if null, just return
if (clazz == null) return 0;
// if empty list, return
if (this.listeners.isEmpty()) return 0;
// loop over the list of listeners
int count = 0;
Iterator listenerIterator = this.listeners.iterator();
while (listenerIterator.hasNext()) {
Listener listener = listenerIterator.next();
if (clazz.isInstance(listener)) {
listenerIterator.remove();
count++;
}
}
return count;
}
/**
* Returns the total number of listeners attached to this world.
* @return int
* @since 3.1.1
*/
public int getListenerCount() {
return this.listeners.size();
}
/**
* Returns the total number of listeners of the given type (or sub types)
* attached to this world.
*
* Returns zero if the given class type is null.
*
* Example usage:
*
* world.getListenerCount(BoundsListener.class);
*
* @param the listener type
* @param clazz the listener type
* @return int
* @since 3.1.1
*/
public int getListenerCount(Class clazz) {
// check for null
if (clazz == null) return 0;
// loop over the listeners
int count = 0;
int lSize = this.listeners.size();
for (int i = 0; i < lSize; i++) {
Listener listener = this.listeners.get(i);
// check if the listener is of the given type
if (clazz.isInstance(listener)) {
// if so, increment
count++;
}
}
// return the count
return count;
}
/**
* Sets the broad-phase collision detection algorithm.
* @param broadphaseDetector the broad-phase collision detection algorithm
* @throws NullPointerException if broadphaseDetector is null
*/
public void setBroadphaseDetector(BroadphaseDetector broadphaseDetector) {
if (broadphaseDetector == null) throw new NullPointerException(Messages.getString("dynamics.world.nullBroadphaseDetector"));
// set the new broadphase
this.broadphaseDetector = broadphaseDetector;
// re-add all bodies to the broadphase
int size = this.bodies.size();
for (int i = 0; i < size; i++) {
this.broadphaseDetector.add(this.bodies.get(i));
}
}
/**
* Returns the broad-phase collision detection algorithm.
* @return {@link BroadphaseDetector} the broad-phase collision detection algorithm
*/
public BroadphaseDetector getBroadphaseDetector() {
return this.broadphaseDetector;
}
/**
* Sets the {@link BroadphaseFilter} used when detecting collisions for each time step.
*
* This should always be an instance of a class that extends the {@link DetectBroadphaseFilter}
* so that the standard filters are retained.
* @param filter the filter
* @since 3.2.2
*/
public void setDetectBroadphaseFilter(BroadphaseFilter
filter) {
if (filter == null) {
this.detectBroadphaseFilter = new DetectBroadphaseFilter();
} else {
this.detectBroadphaseFilter = filter;
}
}
/**
* Returns the {@link BroadphaseFilter} used when detecting collisions for each time step.
* @return {@link BroadphaseFilter}
* @since 3.2.2
*/
public BroadphaseFilter getDetectBroadphaseFilter() {
return this.detectBroadphaseFilter;
}
/**
* Sets the narrow-phase collision detection algorithm.
* @param narrowphaseDetector the narrow-phase collision detection algorithm
* @throws NullPointerException if narrowphaseDetector is null
*/
public void setNarrowphaseDetector(NarrowphaseDetector narrowphaseDetector) {
if (narrowphaseDetector == null) throw new NullPointerException(Messages.getString("dynamics.world.nullNarrowphaseDetector"));
this.narrowphaseDetector = narrowphaseDetector;
}
/**
* Returns the narrow-phase collision detection algorithm.
* @return {@link NarrowphaseDetector} the narrow-phase collision detection algorithm
*/
public NarrowphaseDetector getNarrowphaseDetector() {
return this.narrowphaseDetector;
}
/**
* Sets the manifold solver.
* @param manifoldSolver the manifold solver
* @throws NullPointerException if manifoldSolver is null
*/
public void setManifoldSolver(ManifoldSolver manifoldSolver) {
if (manifoldSolver == null) throw new NullPointerException(Messages.getString("dynamics.world.nullManifoldSolver"));
this.manifoldSolver = manifoldSolver;
}
/**
* Returns the manifold solver.
* @return {@link ManifoldSolver} the manifold solver
*/
public ManifoldSolver getManifoldSolver() {
return this.manifoldSolver;
}
/**
* Sets the time of impact detector.
* @param timeOfImpactDetector the time of impact detector
* @throws NullPointerException if timeOfImpactDetector is null
* @since 1.2.0
*/
public void setTimeOfImpactDetector(TimeOfImpactDetector timeOfImpactDetector) {
if (timeOfImpactDetector == null) throw new NullPointerException(Messages.getString("dynamics.world.nullTimeOfImpactDetector"));
this.timeOfImpactDetector = timeOfImpactDetector;
}
/**
* Returns the time of impact detector.
* @return {@link TimeOfImpactDetector} the time of impact detector
* @since 1.2.0
*/
public TimeOfImpactDetector getTimeOfImpactDetector() {
return this.timeOfImpactDetector;
}
/**
* Sets the raycast detector.
* @param raycastDetector the raycast detector
* @throws NullPointerException if raycastDetector is null
* @since 2.0.0
*/
public void setRaycastDetector(RaycastDetector raycastDetector) {
if (raycastDetector == null) throw new NullPointerException(Messages.getString("dynamics.world.nullRaycastDetector"));
this.raycastDetector = raycastDetector;
}
/**
* Returns the raycast detector.
* @return {@link RaycastDetector} the raycast detector
* @since 2.0.0
*/
public RaycastDetector getRaycastDetector() {
return this.raycastDetector;
}
/**
* Returns the {@link CoefficientMixer}.
* @return {@link CoefficientMixer}
* @see #setCoefficientMixer(CoefficientMixer)
*/
public CoefficientMixer getCoefficientMixer() {
return this.coefficientMixer;
}
/**
* Sets the {@link CoefficientMixer}.
*
* A {@link CoefficientMixer} is an implementation of mixing functions for various
* coefficients used in contact solving. Common coefficients are restitution and
* friction. Since each {@link BodyFixture} can have it's own value for these
* coefficients, the {@link CoefficientMixer} is used to mathematically combine them
* into one coefficient to be used in contact resolution.
*
* {@link CoefficientMixer#DEFAULT_MIXER} is the default.
* @param coefficientMixer the coefficient mixer
* @throws NullPointerException if coefficientMixer is null
* @see CoefficientMixer
*/
public void setCoefficientMixer(CoefficientMixer coefficientMixer) {
if (coefficientMixer == null) throw new NullPointerException(Messages.getString("dynamics.world.nullCoefficientMixer"));
this.coefficientMixer = coefficientMixer;
}
/**
* Sets the {@link ContactManager}.
*
* A {@link ContactManager} manages the contacts detected in the {@link World#detect()} method
* and performs notification of {@link ContactListener}s. {@link ContactManager}s can also contain
* specialized logic for improving performance and simulation quality.
*
* Changing the contact manager requires an update to be performed on the next update of this
* world and any cached information will be lost.
*
* The default is the {@link DefaultContactManager}.
* @param contactManager the contact manager
* @throws NullPointerException if contactManager is null
* @see ContactManager
* @since 3.2.0
*/
public void setContactManager(ContactManager contactManager) {
if (contactManager == null) throw new NullPointerException(Messages.getString("dynamics.world.nullContactManager"));
this.contactManager = contactManager;
this.updateRequired = true;
}
/**
* Returns the {@link ContactManager}.
* @return {@link ContactManager}
* @since 1.0.2
* @see #setContactManager(ContactManager)
*/
public ContactManager getContactManager() {
return this.contactManager;
}
/**
* Sets the {@link ContactConstraintSolver} for this world.
* @param constraintSolver the contact constraint solver
* @throws NullPointerException if contactManager is null
* @see ContactConstraintSolver
* @since 3.2.0
*/
public void setContactConstraintSolver(ContactConstraintSolver constraintSolver) {
if (constraintSolver == null) throw new NullPointerException(Messages.getString("dynamics.world.nullContactConstraintSolver"));
this.contactConstraintSolver = constraintSolver;
}
/**
* Returns the {@link ContactConstraintSolver}.
* @return {@link ContactConstraintSolver}
* @since 3.2.0
* @see #setContactConstraintSolver(ContactConstraintSolver)
*/
public ContactConstraintSolver getContactConstraintSolver() {
return this.contactConstraintSolver;
}
/* (non-Javadoc)
* @see org.dyn4j.DataContainer#getUserData()
*/
public Object getUserData() {
return this.userData;
}
/* (non-Javadoc)
* @see org.dyn4j.DataContainer#setUserData(java.lang.Object)
*/
public void setUserData(Object userData) {
this.userData = userData;
}
/**
* Returns the number of {@link Body}s in this {@link World}.
* @return int the number of bodies
*/
public int getBodyCount() {
return this.bodies.size();
}
/**
* Returns the {@link Body} at the given index.
* @param index the index
* @return {@link Body}
*/
public Body getBody(int index) {
return this.bodies.get(index);
}
/**
* Returns an unmodifiable list containing all the bodies in this world.
*
* The returned list is backed by the internal list, therefore adding or removing bodies while
* iterating through the returned list is not permitted. Use the {@link #getBodyIterator()}
* method instead.
* @return List<{@link Body}>
* @since 3.1.5
* @see #getBodyIterator()
*/
public List
getBodies() {
return Collections.unmodifiableList(this.bodies);
}
/**
* Returns an iterator for iterating over the bodies in this world.
*
* The returned iterator supports the remove
method.
* @return Iterator<{@link Body}>
* @since 3.2.0
*/
public Iterator
getBodyIterator() {
return new BodyIterator(this);
}
/**
* Returns the number of {@link Joint}s in this {@link World}.
* @return int the number of joints
*/
public int getJointCount() {
return this.joints.size();
}
/**
* Returns the {@link Joint} at the given index.
* @param index the index
* @return {@link Joint}
*/
public Joint getJoint(int index) {
return this.joints.get(index);
}
/**
* Returns an unmodifiable list containing all the joints in this world.
*
* The returned list is backed by the internal list, therefore adding or removing joints while
* iterating through the returned list is not permitted. Use the {@link #getJointIterator()}
* method instead.
* @return List<{@link Joint}>
* @since 3.1.5
* @see #getJointIterator()
*/
public List getJoints() {
return Collections.unmodifiableList(this.joints);
}
/**
* Returns an iterator for iterating over the joints in this world.
*
* The returned iterator supports the remove
method.
* @return Iterator<{@link Joint}>
* @since 3.2.0
*/
public Iterator getJointIterator() {
return new JointIterator(this);
}
/**
* Returns the {@link Step} object used to advance
* the simulation.
*
* The returned object contains the step information (elapsed time)
* for the last and the previous time step.
* @return {@link Step} the current step object
*/
public Step getStep() {
return this.step;
}
/**
* Returns true if this world doesn't contain any
* bodies or joints.
* @return boolean
* @since 3.0.1
*/
public boolean isEmpty() {
int bSize = this.bodies.size();
int jSize = this.joints.size();
return bSize == 0 && jSize == 0;
}
/**
* Returns the current accumulated time.
*
* This is the time that has elapsed since the last step
* of the engine.
*
* This time is used and/or accumulated on each call of the
* {@link #update(double)} and {@link #update(double, int)} methods.
*
* This time is reduced by the step frequency for each step
* of the engine.
* @return double
* @since 3.1.10
*/
public double getAccumulatedTime() {
return this.time;
}
/**
* Sets the current accumulated time.
*
* A typical use case would be to throw away any remaining time
* that the {@link #update(double)} or {@link #update(double, int)}
* methods didn't use:
*
* boolean updated = world.update(elapsedTime);
* // the check if the world actually updated is crutial in this example
* if (updated) {
* // throw away any remaining time we didnt use
* world.setAccumulatedTime(0);
* }
*
* Or, in the case of reusing the same World object, you could use this
* method to clear any accumulated time.
*
* If elapsedTime is less than zero, this method immediately returns.
* @see #getAccumulatedTime()
* @param elapsedTime the desired elapsed time
* @since 3.1.10
*/
public void setAccumulatedTime(double elapsedTime) {
if (elapsedTime < 0.0) return;
this.time = elapsedTime;
}
private final class BodyModificationHandler implements FixtureModificationHandler {
private final Body body;
public BodyModificationHandler(Body body) {
this.body = body;
}
@Override
public void onFixtureAdded(BodyFixture fixture) {
World.this.broadphaseDetector.add(this.body, fixture);
}
@Override
public void onFixtureRemoved(BodyFixture fixture) {
World.this.broadphaseDetector.remove(this.body, fixture);
}
@Override
public void onAllFixturesRemoved() {
World.this.broadphaseDetector.remove(this.body);
}
}
}