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