PBox2D
The PBox2D class is a simple wrapper to help integrate the JBox2D world with ruby implementations of processing (JRubyArt, PiCrate and propane). Use the [WorldBuilder][world_builder] in your sketch setup to create an instance of this class.
# Ruby version of java wrapper allows us to have more
# rubified interface, also needed for add_listener
class Box2D < Java::ProcessingBox2d::Box2DProcessing
field_accessor :world # allow access to protected variable
def init_options(args = {})
args = defaults.merge(args)
set_options(args[:scale],
args[:gravity].to_java(Java::float),
args[:warm],
args[:continuous]
)
end
def step_options(args = {})
default_step.merge(args)
set_step(args[:time_step], args[:velocity_iter], args[:position_iter])
end
def defaults
{ scale: 10.0, gravity: [0, -10], warm: true, continuous: true }
end
def default_step
{ time_step: 1.0 / 60, velocity_iter: 8, position_iter: 10 }
end
def gravity(args)
change_gravity(args.to_java(Java::float))
end
def add_listener(listener)
# in combination with field accessor we can access protected world
world.setContactListener(listener)
end
def version
format('pbox2d version %s', Pbox2d::VERSION)
end
end
The java class
package processing.box2d;
import org.jbox2d.common.Transform;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.Joint;
import org.jbox2d.dynamics.joints.JointDef;
import processing.core.PApplet;
/**
* Loosely based on Box2D-for-processing by Dan Shiffman
*
* @author Martin Prout
*/
public abstract class Box2DProcessing {
private final PApplet parent;
private Options options;
private Step stepO;
private final float height;
private final float width;
/**
* The Box2D world (we might need public access for our ContactListener)
*/
protected World world;
/**
* Scale between processing sketch and physics world
*/
private float scaleFactor;// = 10.0f;
/**
* Adjust for processing.org unfathomable choice of y-axis direction
*/
private final int yFlip;// = -1; //flip y coordinate
/**
* Controls access to processing pre loop (via reflection)
*/
private boolean isActive = false;
private Body groundBody;
/**
*
* @param p PApplet
*/
public Box2DProcessing(PApplet p) {
parent = p;
height = p.height;
width = p.width;
yFlip = -1;
setActive(true);
}
/**
* Abstract method implement on ruby side
*
* @param listener Custom Listener, Sketch?
*/
public abstract void addListener(org.jbox2d.callbacks.ContactListener listener);
/**
*
* @param scale float
* @param gravity float[]
* @param warmStart boolean
* @param continuous boolean
*/
protected void setOptions(float scale, float[] gravity, boolean warmStart, boolean continuous) {
options = new Options(scale, gravity, warmStart, continuous);
}
/**
*
* @param timeStep float
* @param velocity int
* @param position int
*/
protected void setStep(float timeStep, int velocity, int position) {
stepO = new Step(timeStep, velocity, position);
}
/**
* This is the all important physics "step" function Says to move ahead one
* unit in time Default
*/
protected void step() {
if (stepO == null) {
stepO = new Step();
}
world.step(stepO.timeStep, stepO.velIters, stepO.posIters);
world.clearForces();
}
/**
* Create a world
*/
public void createWorld() {
if (options == null) {
options = new Options();
}
Vec2 gravity = new Vec2(options.gravity[0], options.gravity[1]);
scaleFactor = options.scaleFactor;
world = new World(gravity);
world.setWarmStarting(options.warm);
world.setContinuousPhysics(options.continuous);
BodyDef bodyDef = new BodyDef();
groundBody = world.createBody(bodyDef);
}
/**
*
* @return Body
*/
public Body groundBody() {
return groundBody;
}
/**
* Set the gravity (this can change in real-time)
*
* @param gravity float[]
*/
protected void changeGravity(float[] gravity) {
world.setGravity(new Vec2(gravity[0], gravity[1]));
}
/**
* Box2d has its own coordinate system and we have to move back and forth
* between them to convert from Box2d world to processing pixel space
*
* @param world Vec2
* @return Vec2
*/
public Vec2 worldToProcessing(Vec2 world) {
return worldToProcessing(world.x, world.y);
}
/**
* Box2d has its own coordinate system and we have to move back and forth
* between them to convert from Box2d world to processing pixel space
* Note reverse Y mapping (processing poxy coord system again)
* @param worldX float
* @param worldY float
* @return Vec2
*/
public Vec2 worldToProcessing(float worldX, float worldY) {
float pixelX = map(worldX, 0f, 1f, parent.width / 2, parent.width / 2 + scaleFactor);
float pixelY = map(worldY, 1f, 0f, parent.height / 2, parent.height / 2 + scaleFactor);
return new Vec2(pixelX, pixelY);
}
/**
* convert Coordinate from pixel space to box2d world
*
* @param screen Vec2
* @return Vec2
*/
public Vec2 processingToWorld(Vec2 screen) {
return processingToWorld(screen.x, screen.y);
}
/**
* Note reverse Y mapping (processing poxy coord system again)
* @param pixelX float
* @param pixelY float
* @return Vec2
*/
public Vec2 processingToWorld(float pixelX, float pixelY) {
float worldX = map(pixelX, parent.width / 2, parent.width / 2 + scaleFactor, 0f, 1f);
float worldY = map(pixelY, parent.height / 2, parent.height / 2 + scaleFactor, 1f, 0f);
return new Vec2(worldX, worldY);
}
/**
* Scale from processing to world
*
* @param val float
* @return float
*/
public float scaleToWorld(float val) {
return val / scaleFactor;
}
/**
* Scale from world to processing
*
* @param val float
* @return float
*/
public float scaleToProcessing(float val) {
return val * scaleFactor;
}
/**
* Vector scale between two worlds
*
* @param v Vec2
* @return Vec2
*/
public Vec2 vectorToWorld(Vec2 v) {
Vec2 u = new Vec2(v.x / scaleFactor, v.y / scaleFactor);
u.y *= yFlip;
return u;
}
/**
* Translate from world coords to processing as a Vec2
*
* @param x float
* @param y float
* @return Vec
*/
public Vec2 vectorToWorld(float x, float y) {
Vec2 u = new Vec2(x / scaleFactor, y / scaleFactor);
u.y *= yFlip;
return u;
}
/**
* Translate from world to processing as a Vec2
*
* @param v Vec
* @return Vec
*/
public Vec2 vectorToProcessing(Vec2 v) {
Vec2 u = new Vec2(v.x * scaleFactor, v.y * scaleFactor);
u.y *= yFlip;
return u;
}
/**
* A common task we have to do a lot
*
* @param bd BodyDef
* @return Body
*/
public Body createBody(BodyDef bd) {
return world.createBody(bd);
}
/**
* A common task we have to do a lot
*
* @param jd JointDef
* @return World
*/
public Joint createJoint(JointDef jd) {
return world.createJoint(jd);
}
/**
*
* @param b Body
* @return body coord as Vec2
*/
public Vec2 bodyCoord(Body b) {
Transform xf = b.getTransform();
return worldToProcessing(xf.p);
}
/**
*
* @param b Body
*/
public void destroyBody(Body b) {
world.destroyBody(b);
}
/**
* Access the processing pre loop by java reflection
*/
public void pre() {
step();
}
/**
* Recommended inclusion in a processing library
*/
public void dispose() {
setActive(false);
}
/**
*
* @return height float
*/
public float height() {
return height;
}
/**
*
* @return width float
*/
public float width() {
return width;
}
private float map(float val, float startIn, float endIn, float startOut, float endOut) {
return startOut + (endOut - startOut) * ((val - startIn) / (endIn - startIn));
}
private void setActive(boolean active) {
if (active != isActive) {
isActive = active;
if (active) {
parent.registerMethod("dispose", this);
parent.registerMethod("pre", this);
} else {
parent.unregisterMethod("pre", this);
}
}
}
}