/* * 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 org.jbox2d.collision.MassData; import org.jbox2d.collision.Shape; import org.jbox2d.collision.ShapeDef; import org.jbox2d.common.*; import org.jbox2d.dynamics.contacts.ContactEdge; import org.jbox2d.dynamics.joints.JointEdge; // Updated to rev. 54->118->142 of b2Body.cpp/.h // Rewritten completely for rev. 118 (too many changes, needed reorganization for maintainability) /** * A 2-dimensional rigid body. Do not create Body objects directly; * instead, pass a BodyDef to either World::createStaticBody or * World::createDynamicBody and then call Body::createShape(ShapeDef) * to add geometry. For a dynamic body, don't forget to call * Body::setMassFromShapes or (for experts) Body::setMass(MassData) - * if you forget to set the mass, the simulation will have problems. *

* When possible, quantities of interest should be accessed via * getters/setters rather than through the m_* variables. These are * internal variables, and their use is generally unsupported. */ public class Body { //m_flags public static final int e_frozenFlag = 0x0002; public static final int e_islandFlag = 0x0004; public static final int e_sleepFlag = 0x0008; public static final int e_allowSleepFlag = 0x0010; public static final int e_bulletFlag = 0x0020; public static final int e_fixedRotationFlag = 0x0040; public int m_flags; //m_type public static final int e_staticType = 0; public static final int e_dynamicType = 1; public static final int e_maxTypes = 2; public int m_type; /** The body origin transform */ public XForm m_xf; /** The swept motion for CCD */ public Sweep m_sweep; public Vec2 m_linearVelocity; public float m_angularVelocity; public Vec2 m_force; public float m_torque; public World m_world; public Body m_prev; public Body m_next; public Shape m_shapeList; public int m_shapeCount; public JointEdge m_jointList; public ContactEdge m_contactList; public float m_mass, m_invMass; public float m_I, m_invI; public float m_linearDamping; public float m_angularDamping; public float m_sleepTime; /** * A holder to attach external data to a body. * Useful to keep track of what game entity * each body represents. This is copied from * the BodyDef used to create the body, so may * be set there instead. */ public Object m_userData; /** * Should not be called by user, as it will not * be properly added to the world. Instead, * create a BodyDef object and pass it * to World.createDynamicBody or World.createStaticBody. * * @param bd Body definition * @param world World to create body in */ public Body(BodyDef bd, World world) { //assert(world.m_lock == false); m_flags = 0; if (bd.isBullet) m_flags |= e_bulletFlag; if (bd.fixedRotation) m_flags |= e_fixedRotationFlag; if (bd.allowSleep) m_flags |= e_allowSleepFlag; if (bd.isSleeping) m_flags |= e_sleepFlag; m_world = world; m_xf = new XForm(); m_xf.position.set(bd.position); m_xf.R.set(bd.angle); m_sweep = new Sweep(); m_sweep.localCenter.set(bd.massData.center); m_sweep.t0 = 1.0f; m_sweep.a0 = m_sweep.a = bd.angle; m_sweep.c.set(XForm.mul(m_xf, m_sweep.localCenter)); m_sweep.c0.set(m_sweep.c); m_jointList = null; m_contactList = null; m_prev = null; m_next = null; m_linearDamping = bd.linearDamping; m_angularDamping = bd.angularDamping; m_force = new Vec2(0.0f, 0.0f); m_torque = 0.0f; m_linearVelocity = new Vec2(0.0f, 0.0f); m_angularVelocity = 0.0f; m_sleepTime = 0.0f; m_invMass = 0.0f; m_I = 0.0f; m_invI = 0.0f; m_mass = bd.massData.mass; if (m_mass > 0.0f) { m_invMass = 1.0f / m_mass; } if ((m_flags & Body.e_fixedRotationFlag) == 0) { m_I = bd.massData.I; } if (m_I > 0.0f) { m_invI = 1.0f / m_I; } if (m_invMass == 0.0f && m_invI == 0.0f) { m_type = e_staticType; } else { m_type = e_dynamicType; } m_userData = bd.userData; m_shapeList = null; m_shapeCount = 0; } /** * Creates a shape and attach it to this body. *
Warning: This function is locked during callbacks. * @param def the shape definition. */ public Shape createShape(ShapeDef def){ //assert(m_world.m_lock == false); if (m_world.m_lock == true){ return null; } Shape s = Shape.create(def); s.m_next = m_shapeList; m_shapeList = s; ++m_shapeCount; s.m_body = this; // Add the shape to the world's broad-phase. s.createProxy(m_world.m_broadPhase, m_xf); // Compute the sweep radius for CCD. s.updateSweepRadius(m_sweep.localCenter); return s; } /** * Destroy a shape. This removes the shape from the broad-phase and * therefore destroys any contacts associated with this shape. All shapes * attached to a body are implicitly destroyed when the body is destroyed. *
Warning: This function is locked during callbacks. * @param s the shape to be removed. */ public void destroyShape(Shape s){ //assert(m_world.m_lock == false); if (m_world.m_lock == true) { return; } //assert(s.getBody() == this); s.destroyProxy(m_world.m_broadPhase); //assert(m_shapeCount > 0); // Remove s from linked list, fix up connections // TODO: verify that this works right Shape node = m_shapeList; Shape prevNode = null; boolean found = false; while (node != null) { if (node == s) { if (prevNode == null) { m_shapeList = s.m_next; found = true; break; } else { prevNode.m_next = s.m_next; found = true; break; } } prevNode = node; node = node.m_next; } /* Shape** node = &m_shapeList; boolean found = false; while (*node != NULL) { if (*node == s) { *node = s->m_next; found = true; break; } node = &(*node)->m_next; } */ // You tried to remove a shape that is not attached to this body. //assert(found); s.m_body = null; s.m_next = null; --m_shapeCount; Shape.destroy(s); } /** * Set the mass properties. Note that this changes the center of mass position. * If you are not sure how to compute mass properties, use setMassFromShapes(). * The inertia tensor is assumed to be relative to the center of mass. * @param massData the mass properties. */ public void setMass(MassData massData){ //assert(m_world.m_lock == false); if (m_world.m_lock == true) return; m_invMass = 0.0f; m_I = 0.0f; m_invI = 0.0f; m_mass = massData.mass; if (m_mass > 0.0f) { m_invMass = 1.0f / m_mass; } if ((m_flags & Body.e_fixedRotationFlag) == 0) { m_I = massData.I; } if (m_I > 0.0f) { m_invI = 1.0f / m_I; } // Move center of mass. m_sweep.localCenter.set(massData.center); m_sweep.c.set(XForm.mul(m_xf, m_sweep.localCenter)); m_sweep.c0.set(m_sweep.c); // Update the sweep radii of all child shapes for (Shape s = m_shapeList; s != null; s = s.m_next) { s.updateSweepRadius(m_sweep.localCenter); } int oldType = m_type; if (m_invMass == 0.0f && m_invI == 0.0f) { m_type = e_staticType; } else { m_type = e_dynamicType; } // If the body type changed, we need to refilter the broad-phase proxies. if (oldType != m_type) { for (Shape s = m_shapeList; s != null; s = s.m_next) { s.refilterProxy(m_world.m_broadPhase, m_xf); } } } /** * Compute the mass properties from the attached shapes. You typically call this * after adding all the shapes. If you add or remove shapes later, you may want * to call this again. Note that this changes the center of mass position. */ public void setMassFromShapes(){ //assert(m_world.m_lock == false); if (m_world.m_lock == true) return; // Compute mass data from shapes. Each shape has its own density. m_mass = 0.0f; m_invMass = 0.0f; m_I = 0.0f; m_invI = 0.0f; Vec2 center = new Vec2(0.0f, 0.0f); for (Shape s = m_shapeList; s != null; s = s.m_next) { MassData massData = new MassData(); s.computeMass(massData); m_mass += massData.mass; center.x += massData.mass * massData.center.x; center.y += massData.mass * massData.center.y; m_I += massData.I; } // Compute center of mass, and shift the origin to the COM if (m_mass > 0.0f) { m_invMass = 1.0f / m_mass; center.x *= m_invMass; center.y *= m_invMass; } if (m_I > 0.0f && (m_flags & e_fixedRotationFlag) == 0) { // Center the inertia about the center of mass m_I -= m_mass * Vec2.dot(center, center); //assert(m_I > 0.0f); m_invI = 1.0f / m_I; } else { m_I = 0.0f; m_invI = 0.0f; } // Move center of mass m_sweep.localCenter.set(center); m_sweep.c.set(XForm.mul(m_xf, m_sweep.localCenter)); m_sweep.c0.set(m_sweep.c); // Update the sweep radii of all child shapes for (Shape s = m_shapeList; s != null; s = s.m_next) { s.updateSweepRadius(m_sweep.localCenter); } int oldType = m_type; if (m_invMass == 0.0f && m_invI == 0.0f) { m_type = e_staticType; } else { m_type = e_dynamicType; } // If the body type changed, we need to refilter the broad-phase proxies. if (oldType != m_type) { for (Shape s = m_shapeList; s != null; s = s.m_next) { s.refilterProxy(m_world.m_broadPhase, m_xf); } } } /** * Set the position of the body's origin and rotation (radians). * This breaks any contacts and wakes the other bodies. * @param position the new world position of the body's origin (not necessarily * the center of mass). * @param angle the new world rotation angle of the body in radians. * @return false if the movement put a shape outside the world. In this case the * body is automatically frozen. */ public boolean setXForm(Vec2 position, float angle){ //assert(m_world.m_lock == false); if (m_world.m_lock == true) return true; if (isFrozen()) return false; m_xf.R.set(angle); m_xf.position.set(position); m_sweep.c.set(XForm.mul(m_xf, m_sweep.localCenter)); m_sweep.c0.set(m_sweep.c); m_sweep.a0 = m_sweep.a = angle; boolean freeze = false; for (Shape s = m_shapeList; s != null; s = s.m_next) { boolean inRange = s.synchronize(m_world.m_broadPhase, m_xf, m_xf); if (inRange == false) { freeze = true; break; } } if (freeze == true) { m_flags |= e_frozenFlag; m_linearVelocity.setZero(); m_angularVelocity = 0.0f; for (Shape s = m_shapeList; s != null; s = s.m_next) { s.destroyProxy(m_world.m_broadPhase); } // Failure return false; } // Success m_world.m_broadPhase.commit(); return true; } /** * Get a copy of the body transform for the body's origin. * @return the world transform of the body's origin. */ public XForm getXForm(){ XForm xf = new XForm(); xf.set(m_xf); return xf; } /** * Get a copy of the world body origin position. This * is not necessarily the same as the center of mass. * @return the world position of the body's origin. */ public Vec2 getPosition(){ return m_xf.position.clone(); } /** * Get the angle in radians. * @return the current world rotation angle in radians. */ public float getAngle(){ return m_sweep.a; } /** Get a copy of the world position of the center of mass. */ public Vec2 getWorldCenter(){ return m_sweep.c.clone(); } /** Get a copy of the local position of the center of mass. */ public Vec2 getLocalCenter(){ return m_sweep.localCenter.clone(); } /** * Set the linear velocity of the center of mass. * @param v the new linear velocity of the center of mass. */ public void setLinearVelocity(Vec2 v){ m_linearVelocity.set(v); } /** * Get a copy of the linear velocity of the center of mass. * @return the linear velocity of the center of mass. */ public Vec2 getLinearVelocity(){ return m_linearVelocity.clone(); } /** * Set the angular velocity. * @param omega the new angular velocity in radians/second. */ public void setAngularVelocity(float omega){ m_angularVelocity = omega; } /** * Get the angular velocity. * @return the angular velocity in radians/second. */ public float getAngularVelocity(){ return m_angularVelocity; } /** * Apply a force at a world point. If the force is not * applied at the center of mass, it will generate a torque and * affect the angular velocity. This wakes up the body. * @param force the world force vector, usually in Newtons (N). * @param point the world position of the point of application. */ public void applyForce(Vec2 force, Vec2 point){ if (isSleeping()) wakeUp(); m_force.addLocal(force); m_torque += Vec2.cross(point.sub(m_sweep.c), force); } /** * Apply a torque. This affects the angular velocity * without affecting the linear velocity of the center of mass. * This wakes up the body. * @param torque about the z-axis (out of the screen), usually in N-m. */ public void applyTorque(float torque){ if (isSleeping()) wakeUp(); m_torque += torque; } /** * Apply an impulse at a point. This immediately modifies the velocity. * It also modifies the angular velocity if the point of application * is not at the center of mass. This wakes up the body. * @param impulse the world impulse vector, usually in N-seconds or kg-m/s. * @param point the world position of the point of application. */ public void applyImpulse(Vec2 impulse, Vec2 point){ if (isSleeping()) wakeUp(); m_linearVelocity.x += m_invMass * impulse.x; m_linearVelocity.y += m_invMass * impulse.y; m_angularVelocity += m_invI * Vec2.cross(point.sub(m_sweep.c), impulse); } /** * Get the total mass of the body. * @return the mass, usually in kilograms (kg). */ public float getMass(){ return m_mass; } /** * Get the central rotational inertia of the body. * @return the rotational inertia, usually in kg-m^2. */ public float getInertia(){ return m_I; } /** * Get the world coordinates of a point given the local coordinates. * @param localPoint a point on the body measured relative the the body's origin. * @return the same point expressed in world coordinates. */ public Vec2 getWorldPoint(Vec2 localPoint){ return XForm.mul(m_xf, localPoint); } /** * Get the world coordinates of a vector given the local coordinates. * @param localVector a vector fixed in the body. * @return the same vector expressed in world coordinates. */ public Vec2 getWorldVector(Vec2 localVector){ return Mat22.mul(m_xf.R, localVector); } /** * Gets a local point relative to the body's origin given a world point. * @param worldPoint a point in world coordinates. * @return the corresponding local point relative to the body's origin. */ public Vec2 getLocalPoint(Vec2 worldPoint){ return XForm.mulT(m_xf, worldPoint); } /** * Gets a local vector given a world vector. * @param worldVector a vector in world coordinates. * @return the corresponding local vector. */ public Vec2 getLocalVector(Vec2 worldVector){ return Mat22.mulT(m_xf.R, worldVector); } /** Is this body treated like a bullet for continuous collision detection? */ public boolean isBullet(){ return (m_flags & e_bulletFlag) == e_bulletFlag; } /** * Should this body be treated like a bullet for continuous collision detection? * Use sparingly, as continuous collision detection can be expensive. */ public void setBullet(boolean flag){ if (flag) { m_flags |= e_bulletFlag; } else { m_flags &= ~e_bulletFlag; } } /** Is this body static (immovable)? */ public boolean isStatic(){ return m_type == e_staticType; } /** Is this body dynamic (movable)? */ public boolean isDynamic(){ return m_type == e_dynamicType; } /** Is this body frozen? */ public boolean isFrozen(){ return (m_flags & e_frozenFlag) == e_frozenFlag; } /** Is this body sleeping (not simulating). */ public boolean isSleeping(){ return (m_flags & e_sleepFlag) == e_sleepFlag; } /** Set to false to prevent this body from sleeping due to inactivity. */ public void allowSleeping(boolean flag){ if (flag) { m_flags |= e_allowSleepFlag; } else { m_flags &= ~e_allowSleepFlag; wakeUp(); } } /** Wake up this body so it will begin simulating. */ public void wakeUp(){ m_flags &= ~e_sleepFlag; m_sleepTime = 0.0f; } /** * Get the linked list of all shapes attached to this body. * @return first Shape in linked list */ public Shape getShapeList(){ return m_shapeList; } /** * Get the linked list of all joints attached to this body. * @return first JointEdge in linked list */ public JointEdge getJointList(){ return m_jointList; } /** * Get the linked list of all contacts attached to this body. * @return first ContactEdge in linked list */ /* Removed from C++ version public ContactEdge getContactList(){ return m_contactList; }*/ /** Get the next body in the world's body list. */ public Body getNext(){ return m_next; } /** Get the user data Object reference that was provided in the body definition. */ public Object getUserData(){ return m_userData; } /* INTERNALS BELOW */ /** For internal use only. */ public void computeMass(){ //seems to be missing from C++ version... } /** For internal use only. */ public boolean synchronizeShapes(){ XForm xf1 = new XForm(); xf1.R.set(m_sweep.a0); xf1.position.set(m_sweep.c0.sub(Mat22.mul(xf1.R, m_sweep.localCenter))); boolean inRange = true; for (Shape s = m_shapeList; s != null; s = s.m_next) { inRange = s.synchronize(m_world.m_broadPhase, xf1, m_xf); if (inRange == false) break; } if (inRange == false) { m_flags |= e_frozenFlag; m_linearVelocity.setZero(); m_angularVelocity = 0.0f; for (Shape s = m_shapeList; s != null; s = s.m_next) { s.destroyProxy(m_world.m_broadPhase); } // Failure return false; } // Success return true; } /** For internal use only. */ public void synchronizeTransform(){ m_xf.R.set(m_sweep.a); //m_xf.position.set(m_sweep.c.sub(Mat22.mul(m_xf.R,m_sweep.localCenter))); Vec2 v1 = m_sweep.localCenter; m_xf.position.x = m_sweep.c.x - (m_xf.R.col1.x * v1.x + m_xf.R.col2.x * v1.y); m_xf.position.y = m_sweep.c.y - (m_xf.R.col1.y * v1.x + m_xf.R.col2.y * v1.y); //System.out.println(m_xf); } /** * This is used to prevent connected bodies from colliding. * It may lie, depending on the collideConnected flag, so * it won't be very useful external to the engine. */ public boolean isConnected(Body other){ for (JointEdge jn = m_jointList; jn != null; jn = jn.next) { if (jn.other == other) { //System.out.println("connected"); return (jn.joint.m_collideConnected == false); } } return false; } /** For internal use only. */ public void advance(float t){ // Advance to the new safe time m_sweep.advance(t); m_sweep.c.set(m_sweep.c0); m_sweep.a = m_sweep.a0; synchronizeTransform(); } /** * Get the world linear velocity of a world point attached to this body. * @param worldPoint a point in world coordinates. * @return the world velocity of a point. */ public Vec2 getLinearVelocityFromWorldPoint(Vec2 worldPoint) { return m_linearVelocity.add(Vec2.cross(m_angularVelocity, worldPoint.sub(m_sweep.c))); } /** * Get the world velocity of a local point. * @param localPoint a point in local coordinates. * @return the world velocity of a point. */ public Vec2 getLinearVelocityFromLocalPoint(Vec2 localPoint) { return getLinearVelocityFromWorldPoint(getWorldPoint(localPoint)); } /** * Put this body to sleep so it will stop simulating. * This also sets the velocity to zero. */ public void putToSleep() { m_flags |= e_sleepFlag; m_sleepTime = 0.0f; m_linearVelocity.setZero(); m_angularVelocity = 0.0f; m_force.setZero(); m_torque = 0.0f; } public void setUserData(Object data) { m_userData = data; } public World getWorld() { return m_world; } }