/*
* JBox2D - A Java Port of Erin Catto's Box2D
*
* JBox2D homepage: http://jbox2d.sourceforge.net/
* Box2D homepage: http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
package org.jbox2d.dynamics;
//import java.util.ArrayList;
import java.util.Vector;
import java.util.Enumeration;
import org.jbox2d.common.Color3f;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.BroadPhase;
import org.jbox2d.collision.CircleShape;
import org.jbox2d.collision.OBB;
import org.jbox2d.collision.Pair;
import org.jbox2d.collision.PairManager;
import org.jbox2d.collision.PolygonShape;
import org.jbox2d.collision.Proxy;
import org.jbox2d.collision.Shape;
import org.jbox2d.collision.ShapeType;
import org.jbox2d.collision.TOI;
import org.jbox2d.common.*;
import org.jbox2d.dynamics.contacts.Contact;
import org.jbox2d.dynamics.contacts.ContactEdge;
import org.jbox2d.dynamics.joints.*;
//Updated to rev 56->118->142->150 of b2World.cpp/.h
/**
* The world that physics takes place in.
*
* To the extent that it is possible, avoid accessing members
* directly, as in a future version their accessibility may
* be rolled back - as un-Java as that is, we must follow
* upstream C++ conventions, and for now everything is public
* to speed development of Box2d, but it is subject to change.
* You're warned!
*/
public class World {
boolean m_lock;
BroadPhase m_broadPhase;
ContactManager m_contactManager;
Body m_bodyList;
/** Do not access, won't be useful! */
Contact m_contactList;
Joint m_jointList;
int m_bodyCount;
int m_contactCount;
int m_jointCount;
Vec2 m_gravity;
boolean m_allowSleep;
Body m_groundBody;
int m_positionIterationCount;
/** Should we apply position correction? */
boolean m_positionCorrection;
/** Should we use warm-starting? Improves stability in stacking scenarios. */
boolean m_warmStarting;
/** Should we enable continuous collision detection? */
boolean m_continuousPhysics;
DestructionListener m_destructionListener;
BoundaryListener m_boundaryListener;
ContactFilter m_contactFilter;
ContactListener m_contactListener;
DebugDraw m_debugDraw;
private float m_inv_dt0;
private Vector postStepList;
/** Get the number of bodies. */
public int getBodyCount() {
return m_bodyCount;
}
/** Get the number of joints. */
public int getJointCount() {
return m_jointCount;
}
/** Get the number of contacts (each may have 0 or more contact points). */
public int getContactCount() {
return m_contactCount;
}
/** Change the global gravity vector. */
public void setGravity(Vec2 gravity) {
m_gravity = gravity;
}
/** Get a clone of the global gravity vector.
* @return Clone of gravity vector
*/
public Vec2 getGravity() {
return m_gravity.clone();
}
/** The world provides a single static ground body with no collision shapes.
* You can use this to simplify the creation of joints and static shapes.
*/
public Body getGroundBody() {
return m_groundBody;
}
/**
* Get the world body list. With the returned body, use Body.getNext() to get
* the next body in the world list. A NULL body indicates the end of the list.
* @return the head of the world body list.
*/
public Body getBodyList() {
return m_bodyList;
}
/**
* Get the world joint list. With the returned joint, use Joint.getNext() to get
* the next joint in the world list. A NULL joint indicates the end of the list.
* @return the head of the world joint list.
*/
public Joint getJointList() {
return m_jointList;
}
/**
* Construct a world object.
* @param worldAABB a bounding box that completely encompasses all your shapes.
* @param gravity the world gravity vector.
* @param doSleep improve performance by not simulating inactive bodies.
*/
public World(AABB worldAABB, Vec2 gravity, boolean doSleep) {
m_positionCorrection = true;
m_warmStarting = true;
m_continuousPhysics = true;
m_destructionListener = null;
m_boundaryListener = null;
m_contactFilter = ContactFilter.DEFAULT_FILTER;//&b2_defaultFilter;
m_contactListener = null;
m_debugDraw = null;
m_inv_dt0 = 0.0f;
m_bodyList = null;
m_contactList = null;
m_jointList = null;
m_bodyCount = 0;
m_contactCount = 0;
m_jointCount = 0;
m_lock = false;
m_allowSleep = doSleep;
m_gravity = gravity;
m_contactManager = new ContactManager();
m_contactManager.m_world = this;
m_broadPhase = new BroadPhase(worldAABB, m_contactManager);
BodyDef bd = new BodyDef();
m_groundBody = createBody(bd);
postStepList = new Vector(4,20);
}
/** Register a destruction listener. */
public void setDestructionListener(DestructionListener listener) {
m_destructionListener = listener;
}
/** Register a broad-phase boundary listener. */
public void setBoundaryListener(BoundaryListener listener) {
m_boundaryListener = listener;
}
/** Register a contact event listener */
public void setContactListener(ContactListener listener) {
m_contactListener = listener;
}
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (b2_defaultFilter).
*/
public void setContactFilter(ContactFilter filter) {
m_contactFilter = filter;
}
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside the World.step() method, so make sure your renderer is ready to
* consume draw commands when you call step().
*/
public void setDebugDraw(DebugDraw debugDraw) {
m_debugDraw = debugDraw;
}
/**
* Create a body given a definition. No reference to the definition
* is retained. Body will be static unless mass is nonzero.
*
Warning: This function is locked during callbacks.
*/
public Body createBody(BodyDef def) {
//assert(m_lock == false);
if (m_lock == true) {
return null;
}
Body b = new Body(def, this);
// Add to world doubly linked list.
b.m_prev = null;
b.m_next = m_bodyList;
if (m_bodyList != null) {
m_bodyList.m_prev = b;
}
m_bodyList = b;
++m_bodyCount;
return b;
}
/**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
*
Warning: This automatically deletes all associated shapes and joints.
*
Warning: This function is locked during callbacks.
*/
public void destroyBody(Body b) {
//assert(m_bodyCount > 0);
//assert(m_lock == false);
if (m_lock == true) {
return;
}
// Delete the attached joints.
JointEdge jn = b.m_jointList;
while (jn != null) {
JointEdge jn0 = jn;
jn = jn.next;
if (m_destructionListener != null){
m_destructionListener.sayGoodbye(jn0.joint);
}
destroyJoint(jn0.joint);
}
// Delete the attached shapes. This destroys broad-phase
// proxies and pairs, leading to the destruction of contacts.
Shape s = b.m_shapeList;
while (s != null) {
Shape s0 = s;
s = s.m_next;
if (m_destructionListener != null) {
m_destructionListener.sayGoodbye(s0);
}
s0.destroyProxy(m_broadPhase);
Shape.destroy(s0);
}
// Remove world body list.
if (b.m_prev != null) {
b.m_prev.m_next = b.m_next;
}
if (b.m_next != null) {
b.m_next.m_prev = b.m_prev;
}
if (b == m_bodyList) {
m_bodyList = b.m_next;
}
--m_bodyCount;
//b->~b2Body();
}
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
*
Warning This function is locked during callbacks.
*/
public Joint createJoint(JointDef def) {
//assert(m_lock == false);
Joint j = Joint.create(def);
// Connect to the world list.
j.m_prev = null;
j.m_next = m_jointList;
if (m_jointList != null) {
m_jointList.m_prev = j;
}
m_jointList = j;
++m_jointCount;
// Connect to the bodies' doubly linked lists
j.m_node1.joint = j;
j.m_node1.other = j.m_body2;
j.m_node1.prev = null;
j.m_node1.next = j.m_body1.m_jointList;
if (j.m_body1.m_jointList != null) {
j.m_body1.m_jointList.prev = j.m_node1;
}
j.m_body1.m_jointList = j.m_node1;
j.m_node2.joint = j;
j.m_node2.other = j.m_body1;
j.m_node2.prev = null;
j.m_node2.next = j.m_body2.m_jointList;
if (j.m_body2.m_jointList != null) {
j.m_body2.m_jointList.prev = j.m_node2;
}
j.m_body2.m_jointList = j.m_node2;
// If the joint prevents collisions, then reset collision filtering
if (def.collideConnected == false) {
// Reset the proxies on the body with the minimum number of shapes.
Body b = def.body1.m_shapeCount < def.body2.m_shapeCount ? def.body1
: def.body2;
for (Shape s = b.m_shapeList; s != null; s = s.m_next) {
s.refilterProxy(m_broadPhase, b.getXForm());
}
}
return j;
}
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
*
Warning: This function is locked during callbacks.
*/
public void destroyJoint(Joint j) {
//assert(m_lock == false);
boolean collideConnected = j.m_collideConnected;
// Remove from the doubly linked list.
if (j.m_prev != null) {
j.m_prev.m_next = j.m_next;
}
if (j.m_next != null) {
j.m_next.m_prev = j.m_prev;
}
if (j == m_jointList) {
m_jointList = j.m_next;
}
// Disconnect from island graph.
Body body1 = j.m_body1;
Body body2 = j.m_body2;
// Wake up connected bodies.
body1.wakeUp();
body2.wakeUp();
// Remove from body 1
if (j.m_node1.prev != null) {
j.m_node1.prev.next = j.m_node1.next;
}
if (j.m_node1.next != null) {
j.m_node1.next.prev = j.m_node1.prev;
}
if (j.m_node1 == body1.m_jointList) {
body1.m_jointList = j.m_node1.next;
}
j.m_node1.prev = null;
j.m_node1.next = null;
// Remove from body 2
if (j.m_node2.prev != null) {
j.m_node2.prev.next = j.m_node2.next;
}
if (j.m_node2.next != null) {
j.m_node2.next.prev = j.m_node2.prev;
}
if (j.m_node2 == body2.m_jointList) {
body2.m_jointList = j.m_node2.next;
}
j.m_node2.prev = null;
j.m_node2.next = null;
Joint.destroy(j);
//assert m_jointCount > 0;
--m_jointCount;
// If the joint prevents collisions, then reset collision filtering.
if (collideConnected == false) {
// Reset the proxies on the body with the minimum number of shapes.
Body b = body1.m_shapeCount < body2.m_shapeCount ? body1 : body2;
for (Shape s = b.m_shapeList; s != null; s = s.m_next) {
s.refilterProxy(m_broadPhase, b.getXForm());
}
}
}
/**
* Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param dt the amount of time to simulate, this should not vary.
* @param iterations the number of iterations to be used by the constraint solver.
*/
public void step(float dt, int iterations) {
m_lock = true;
TimeStep step = new TimeStep();
step.dt = dt;
step.maxIterations = iterations;
if (dt > 0.0f) {
step.inv_dt = 1.0f / dt;
} else {
step.inv_dt = 0.0f;
}
step.dtRatio = m_inv_dt0 * dt;
step.positionCorrection = m_positionCorrection;
step.warmStarting = m_warmStarting;
// Update contacts.
m_contactManager.collide();
// Integrate velocities, solve velocity constraints, and integrate positions.
if (step.dt > 0.0f) {
solve(step);
}
// Handle TOI events.
if (m_continuousPhysics && step.dt > 0.0f) {
solveTOI(step);
}
// Draw debug information.
drawDebugData();
m_inv_dt0 = step.inv_dt;
m_lock = false;
postStep(dt,iterations);
}
/** Goes through the registered postStep functions and calls them. */
private void postStep(float dt, int iterations) {
for (Enumeration e=postStepList.elements(); e.hasMoreElements(); ) {
Steppable s = (Steppable)e.nextElement();
s.step(dt,iterations);
}
}
/**
* Registers a Steppable object to be stepped
* immediately following the physics step, once
* the locks are lifted.
* @param s
*/
public void registerPostStep(Steppable s) {
postStepList.addElement(s);
}
/**
* Unregisters a method from post-stepping.
* Fails silently if method is not found.
* @param s
*/
public void unregisterPostStep(Steppable s) {
postStepList.removeElement(s);
}
/** Re-filter a shape. This re-runs contact filtering on a shape. */
public void refilter(Shape shape) {
shape.refilterProxy(m_broadPhase, shape.getBody().getXForm());
}
/**
* Query the world for all shapes that potentially overlap the
* provided AABB up to max count.
* The number of shapes found is returned.
* @param aabb the query box.
* @param maxCount the capacity of the shapes array.
* @return array of shapes overlapped, up to maxCount in length
*/
public Shape[] query(AABB aabb, int maxCount) {
Object[] objs = m_broadPhase.query(aabb, maxCount);
Shape[] ret = new Shape[objs.length];
System.arraycopy(objs, 0, ret, 0, objs.length);
//for (int i=0; i 0){
continue;
}
if (seed.isStatic()) {
continue;
}
// Reset island and stack.
island.clear();
int stackCount = 0;
stack[stackCount++] = seed;
seed.m_flags |= Body.e_islandFlag;
// Perform a depth first search (DFS) on the constraint graph.
while (stackCount > 0) {
// Grab the next body off the stack and add it to the island.
Body b = stack[--stackCount];
island.add(b);
// Make sure the body is awake.
b.m_flags &= ~Body.e_sleepFlag;
// To keep islands as small as possible, we don't
// propagate islands across static bodies.
if (b.isStatic()) {
continue;
}
// Search all contacts connected to this body.
for ( ContactEdge cn = b.m_contactList; cn != null; cn = cn.next) {
// Has this contact already been added to an island?
if ( (cn.contact.m_flags & (Contact.e_islandFlag | Contact.e_nonSolidFlag)) > 0) {
continue;
}
// Is this contact touching?
if (cn.contact.getManifoldCount() == 0) {
continue;
}
island.add(cn.contact);
cn.contact.m_flags |= Contact.e_islandFlag;
// Was the other body already added to this island?
Body other = cn.other;
if ((other.m_flags & Body.e_islandFlag) > 0) {
continue;
}
//assert stackCount < stackSize;
stack[stackCount++] = other;
other.m_flags |= Body.e_islandFlag;
}
// Search all joints connect to this body.
for ( JointEdge jn = b.m_jointList; jn != null; jn = jn.next) {
if (jn.joint.m_islandFlag == true) {
continue;
}
island.add(jn.joint);
jn.joint.m_islandFlag = true;
Body other = jn.other;
if ((other.m_flags & Body.e_islandFlag) > 0) {
continue;
}
//assert (stackCount < stackSize);
stack[stackCount++] = other;
other.m_flags |= Body.e_islandFlag;
}
}
island.solve(step, m_gravity, m_positionCorrection, m_allowSleep);
m_positionIterationCount = Math.max(m_positionIterationCount, Island.m_positionIterationCount);
// Post solve cleanup.
for (int i = 0; i < island.m_bodyCount; ++i) {
// Allow static bodies to participate in other islands.
Body b = island.m_bodies[i];
if (b.isStatic()) {
b.m_flags &= ~Body.e_islandFlag;
}
}
}
//m_broadPhase.commit();
// Synchronize shapes, check for out of range bodies.
for (Body b = m_bodyList; b != null; b = b.getNext()) {
if ( (b.m_flags & (Body.e_sleepFlag | Body.e_frozenFlag)) != 0) {
continue;
}
if (b.isStatic()) {
continue;
}
// Update shapes (for broad-phase). If the shapes go out of
// the world AABB then shapes and contacts may be destroyed,
// including contacts that are
boolean inRange = b.synchronizeShapes();
// Did the body's shapes leave the world?
if (inRange == false && m_boundaryListener != null) {
m_boundaryListener.violation(b);
}
}
// Commit shape proxy movements to the broad-phase so that new contacts are created.
// Also, some contacts can be destroyed.
m_broadPhase.commit();
}
/** For internal use: find TOI contacts and solve them. */
public void solveTOI(TimeStep step) {
// Reserve an island and a stack for TOI island solution.
Island island = new Island(m_bodyCount, Settings.maxTOIContactsPerIsland, 0, m_contactListener);
int stackSize = m_bodyCount;
Body[] stack = new Body[stackSize];
for (Body b = m_bodyList; b != null; b = b.m_next) {
b.m_flags &= ~Body.e_islandFlag;
b.m_sweep.t0 = 0.0f;
}
for (Contact c = m_contactList; c != null; c = c.m_next) {
// Invalidate TOI
c.m_flags &= ~(Contact.e_toiFlag | Contact.e_islandFlag);
}
// Find TOI events and solve them.
while (true) {
// Find the first TOI.
Contact minContact = null;
float minTOI = 1.0f;
for (Contact c = m_contactList; c != null; c = c.m_next) {
if ((c.m_flags & (Contact.e_slowFlag | Contact.e_nonSolidFlag)) != 0) {
continue;
}
// TODO_ERIN keep a counter on the contact, only respond to M TOIs per contact.
float toi = 1.0f;
if ((c.m_flags & Contact.e_toiFlag) != 0) {
// This contact has a valid cached TOI.
toi = c.m_toi;
} else {
// Compute the TOI for this contact.
Shape s1 = c.getShape1();
Shape s2 = c.getShape2();
Body b1 = s1.getBody();
Body b2 = s2.getBody();
if ((b1.isStatic() || b1.isSleeping()) && (b2.isStatic() || b2.isSleeping())) {
continue;
}
// Put the sweeps onto the same time interval.
float t0 = b1.m_sweep.t0;
if (b1.m_sweep.t0 < b2.m_sweep.t0) {
t0 = b2.m_sweep.t0;
b1.m_sweep.advance(t0);
} else if (b2.m_sweep.t0 < b1.m_sweep.t0) {
t0 = b1.m_sweep.t0;
b2.m_sweep.advance(t0);
}
//assert(t0 < 1.0f);
// Compute the time of impact.
toi = TOI.timeOfImpact(c.m_shape1, b1.m_sweep, c.m_shape2, b2.m_sweep);
//assert(0.0f <= toi && toi <= 1.0f);
if (toi > 0.0f && toi < 1.0f) {
toi = Math.min((1.0f - toi) * t0 + toi, 1.0f);
}
c.m_toi = toi;
c.m_flags |= Contact.e_toiFlag;
}
if (Settings.EPSILON < toi && toi < minTOI) {
// This is the minimum TOI found so far.
minContact = c;
minTOI = toi;
}
}
if (minContact == null || 1.0f - 100.0f * Settings.EPSILON < minTOI) {
// No more TOI events. Done!
break;
}
// Advance the bodies to the TOI.
Shape s1 = minContact.getShape1();
Shape s2 = minContact.getShape2();
Body b1 = s1.getBody();
Body b2 = s2.getBody();
b1.advance(minTOI);
b2.advance(minTOI);
// The TOI contact likely has some new contact points.
minContact.update(m_contactListener);
minContact.m_flags &= ~Contact.e_toiFlag;
if (minContact.getManifoldCount() == 0) {
// This shouldn't happen. Numerical error?
//b2Assert(false);
continue;
}
// Build the TOI island. We need a dynamic seed.
Body seed = b1;
if (seed.isStatic()) {
seed = b2;
}
// Reset island and stack.
island.clear();
int stackCount = 0;
stack[stackCount++] = seed;
seed.m_flags |= Body.e_islandFlag;
// Perform a depth first search (DFS) on the contact graph.
while (stackCount > 0) {
// Grab the next body off the stack and add it to the island.
Body b = stack[--stackCount];
island.add(b);
// Make sure the body is awake.
b.m_flags &= ~Body.e_sleepFlag;
// To keep islands as small as possible, we don't
// propagate islands across static bodies.
if (b.isStatic()) {
continue;
}
// Search all contacts connected to this body.
for (ContactEdge cn = b.m_contactList; cn != null; cn = cn.next) {
// Does the TOI island still have space for contacts?
if (island.m_contactCount == island.m_contactCapacity) {
continue;
}
// Has this contact already been added to an island? Skip slow or non-solid contacts.
if ( (cn.contact.m_flags & (Contact.e_islandFlag | Contact.e_slowFlag | Contact.e_nonSolidFlag)) != 0) {
continue;
}
// Is this contact touching? For performance we are not updating this contact.
if (cn.contact.getManifoldCount() == 0) {
continue;
}
island.add(cn.contact);
cn.contact.m_flags |= Contact.e_islandFlag;
// Update other body.
Body other = cn.other;
// Was the other body already added to this island?
if ((other.m_flags & Body.e_islandFlag) != 0) {
continue;
}
// March forward, this can do no harm since this is the min TOI.
if (other.isStatic() == false) {
other.advance(minTOI);
other.wakeUp();
}
//assert(stackCount < stackSize);
stack[stackCount++] = other;
other.m_flags |= Body.e_islandFlag;
}
}
TimeStep subStep = new TimeStep();
subStep.dt = (1.0f - minTOI) * step.dt;
//assert(subStep.dt > Settings.EPSILON);
subStep.inv_dt = 1.0f / subStep.dt;
subStep.maxIterations = step.maxIterations;
island.solveTOI(subStep);
// Post solve cleanup.
for (int i = 0; i < island.m_bodyCount; ++i) {
// Allow bodies to participate in future TOI islands.
Body b = island.m_bodies[i];
b.m_flags &= ~Body.e_islandFlag;
if ( (b.m_flags & (Body.e_sleepFlag | Body.e_frozenFlag)) != 0) {
continue;
}
if (b.isStatic()) {
continue;
}
// Update shapes (for broad-phase). If the shapes go out of
// the world AABB then shapes and contacts may be destroyed,
// including contacts that are
boolean inRange = b.synchronizeShapes();
// Did the body's shapes leave the world?
if (inRange == false && m_boundaryListener != null) {
m_boundaryListener.violation(b);
}
// Invalidate all contact TOIs associated with this body. Some of these
// may not be in the island because they were not touching.
for (ContactEdge cn = b.m_contactList; cn != null; cn = cn.next) {
cn.contact.m_flags &= ~Contact.e_toiFlag;
}
}
for (int i = 0; i < island.m_contactCount; ++i) {
// Allow contacts to participate in future TOI islands.
Contact c = island.m_contacts[i];
c.m_flags &= ~(Contact.e_toiFlag | Contact.e_islandFlag);
}
// Commit shape proxy movements to the broad-phase so that new contacts are created.
// Also, some contacts can be destroyed.
m_broadPhase.commit();
}
}
/** For internal use */
public void drawShape(Shape shape, XForm xf, Color3f color, boolean core) {
Color3f coreColor = new Color3f(255f*0.9f, 255f*0.6f, 255f*0.6f);
if (shape.getType() == ShapeType.CIRCLE_SHAPE) {
CircleShape circle = (CircleShape)shape;
Vec2 center = XForm.mul(xf, circle.getLocalPosition());
float radius = circle.getRadius();
Vec2 axis = xf.R.col1;
m_debugDraw.drawSolidCircle(center, radius, axis, color);
if (core) {
m_debugDraw.drawCircle(center, radius - Settings.toiSlop, coreColor);
}
} else if (shape.getType() == ShapeType.POLYGON_SHAPE) {
PolygonShape poly = (PolygonShape)shape;
int vertexCount = poly.getVertexCount();
Vec2[] localVertices = poly.getVertices();
//assert(vertexCount <= Settings.maxPolygonVertices);
Vec2[] vertices = new Vec2[vertexCount];
for (int i = 0; i < vertexCount; ++i) {
vertices[i] = XForm.mul(xf, localVertices[i]);
}
m_debugDraw.drawSolidPolygon(vertices, vertexCount, color);
if (core) {
Vec2[] localCoreVertices = poly.getCoreVertices();
for (int i = 0; i < vertexCount; ++i) {
vertices[i] = XForm.mul(xf, localCoreVertices[i]);
}
m_debugDraw.drawPolygon(vertices, vertexCount, coreColor);
}
}
}
/** For internal use */
public void drawJoint(Joint joint) {
Body b1 = joint.getBody1();
Body b2 = joint.getBody2();
XForm xf1 = b1.getXForm();
XForm xf2 = b2.getXForm();
Vec2 x1 = xf1.position;
Vec2 x2 = xf2.position;
Vec2 p1 = joint.getAnchor1();
Vec2 p2 = joint.getAnchor2();
Color3f color = new Color3f(255f*0.5f, 255f*0.8f, 255f*0.8f);
int type = joint.getType();
if (type == JointType.DISTANCE_JOINT) {
m_debugDraw.drawSegment(p1, p2, color);
} else if (type == JointType.PULLEY_JOINT) {
PulleyJoint pulley = (PulleyJoint)joint;
Vec2 s1 = pulley.getGroundAnchor1();
Vec2 s2 = pulley.getGroundAnchor2();
m_debugDraw.drawSegment(s1, p1, color);
m_debugDraw.drawSegment(s2, p2, color);
m_debugDraw.drawSegment(s1, s2, color);
} else if (type == JointType.MOUSE_JOINT) {
//Don't draw mouse joint
} else {
m_debugDraw.drawSegment(x1, p1, color);
m_debugDraw.drawSegment(p1, p2, color);
m_debugDraw.drawSegment(x2, p2, color);
}
}
/** For internal use */
public void drawDebugData() {
if (m_debugDraw == null) {
return;
}
int flags = m_debugDraw.getFlags();
if ( (flags & DebugDraw.e_shapeBit) != 0) {
boolean core = (flags & DebugDraw.e_coreShapeBit) == DebugDraw.e_coreShapeBit;
for (Body b = m_bodyList; b != null; b = b.getNext()) {
XForm xf = b.getXForm();
for (Shape s = b.getShapeList(); s != null; s = s.getNext()) {
if (s.isSensor()) continue;
if (b.isStatic()) {
drawShape(s, xf, new Color3f(255f*0.5f, 255f*0.9f, 255f*0.5f), core);
}
else if (b.isSleeping()) {
drawShape(s, xf, new Color3f(255f*0.5f, 255f*0.5f, 255f*0.9f), core);
} else {
drawShape(s, xf, new Color3f(255f*0.9f, 255f*0.9f, 255f*0.9f), core);
}
}
}
}
if ( (flags & DebugDraw.e_jointBit) != 0) {
for (Joint j = m_jointList; j != null; j = j.getNext()) {
if (j.getType() != JointType.MOUSE_JOINT) {
drawJoint(j);
}
}
}
if ( (flags & DebugDraw.e_pairBit) != 0) {
BroadPhase bp = m_broadPhase;
Vec2 invQ = new Vec2(0.0f, 0.0f);
invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y);
Color3f color = new Color3f(255f*0.9f, 255f*0.9f, 255f*0.3f);
for (int i = 0; i < PairManager.TABLE_CAPACITY; ++i) {
int index = bp.m_pairManager.m_hashTable[i];
while (index != PairManager.NULL_PAIR) {
Pair pair = bp.m_pairManager.m_pairs[index];
Proxy p1 = bp.m_proxyPool[pair.proxyId1];
Proxy p2 = bp.m_proxyPool[pair.proxyId2];
AABB b1 = new AABB();
AABB b2 = new AABB();
b1.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.lowerBounds[0]].value;
b1.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.lowerBounds[1]].value;
b1.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.upperBounds[0]].value;
b1.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.upperBounds[1]].value;
b2.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.lowerBounds[0]].value;
b2.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.lowerBounds[1]].value;
b2.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.upperBounds[0]].value;
b2.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.upperBounds[1]].value;
Vec2 x1 = new Vec2(0.5f * (b1.lowerBound.x + b1.upperBound.x),
0.5f * (b1.lowerBound.y + b1.upperBound.y));
Vec2 x2 = new Vec2(0.5f * (b2.lowerBound.x + b2.upperBound.x),
0.5f * (b2.lowerBound.y + b2.upperBound.y));
m_debugDraw.drawSegment(x1, x2, color);
index = pair.next;
}
}
}
BroadPhase bp = m_broadPhase;
Vec2 worldLower = bp.m_worldAABB.lowerBound;
Vec2 worldUpper = bp.m_worldAABB.upperBound;
if ( (flags & DebugDraw.e_aabbBit) != 0) {
Vec2 invQ = new Vec2();
invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y);
Color3f color = new Color3f(255f*0.9f, 255f*0.3f,255f* 0.9f);
for (int i = 0; i < Settings.maxProxies; ++i) {
Proxy p = bp.m_proxyPool[i];
if (p.isValid() == false) {
continue;
}
AABB b = new AABB();
b.lowerBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.lowerBounds[0]].value;
b.lowerBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.lowerBounds[1]].value;
b.upperBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.upperBounds[0]].value;
b.upperBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.upperBounds[1]].value;
Vec2[] vs = new Vec2[4];
vs[0] = new Vec2(b.lowerBound.x, b.lowerBound.y);
vs[1] = new Vec2(b.upperBound.x, b.lowerBound.y);
vs[2] = new Vec2(b.upperBound.x, b.upperBound.y);
vs[3] = new Vec2(b.lowerBound.x, b.upperBound.y);
m_debugDraw.drawPolygon(vs, 4, color);
}
}
Vec2[] vsw = new Vec2[4];
vsw[0]= new Vec2(worldLower.x, worldLower.y);
vsw[1]= new Vec2(worldUpper.x, worldLower.y);
vsw[2]= new Vec2(worldUpper.x, worldUpper.y);
vsw[3]= new Vec2(worldLower.x, worldUpper.y);
m_debugDraw.drawPolygon(vsw, 4, new Color3f(255.0f*0.3f, 255.0f*0.9f, 255.0f*0.9f));
if ( (flags & DebugDraw.e_obbBit) != 0) {
Color3f color = new Color3f(0.5f, 0.3f, 0.5f);
for (Body b = m_bodyList; b != null; b = b.getNext()) {
XForm xf = b.getXForm();
for (Shape s = b.getShapeList(); s != null; s = s.getNext()) {
if (s.getType() != ShapeType.POLYGON_SHAPE) {
continue;
}
PolygonShape poly = (PolygonShape)s;
OBB obb = poly.getOBB();
Vec2 h = obb.extents;
Vec2[] vs = new Vec2[4];
vs[0] = new Vec2(-h.x, -h.y);
vs[1] = new Vec2( h.x, -h.y);
vs[2] = new Vec2( h.x, h.y);
vs[3] = new Vec2(-h.x, h.y);
for (int i = 0; i < 4; ++i) {
vs[i] = obb.center.add(Mat22.mul(obb.R, vs[i]));
vs[i] = XForm.mul(xf, vs[i]);
}
m_debugDraw.drawPolygon(vs, 4, color);
}
}
}
if ( (flags & DebugDraw.e_centerOfMassBit) != 0) {
for (Body b = m_bodyList; b != null; b = b.getNext()) {
XForm xf = b.getXForm();
xf.position = b.getWorldCenter();
m_debugDraw.drawXForm(xf);
}
}
}
/** Enable/disable warm starting. For testing. */
public void setWarmStarting(boolean flag) { m_warmStarting = flag; }
/** Enable/disable position correction. For testing. */
public void setPositionCorrection(boolean flag) { m_positionCorrection = flag; }
/** Enable/disable continuous physics. For testing. */
public void setContinuousPhysics(boolean flag) { m_continuousPhysics = flag; }
/** Perform validation of internal data structures. */
public void validate() {
m_broadPhase.validate();
}
/** Get the number of broad-phase proxies. */
public int getProxyCount() {
return m_broadPhase.m_proxyCount;
}
/** Get the number of broad-phase pairs. */
public int getPairCount() {
return m_broadPhase.m_pairManager.m_pairCount;
}
/** Get the world bounding box. */
public AABB getWorldAABB() {
return m_broadPhase.m_worldAABB;
}
/** Return true if the bounding box is within range of the world AABB. */
public boolean inRange(AABB aabb) {
return m_broadPhase.inRange(aabb);
}
}