/** * */ package org.jbox2d.dynamics.controllers; import org.jbox2d.collision.shapes.Shape; import org.jbox2d.common.Color3f; import org.jbox2d.common.Settings; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.DebugDraw; import org.jbox2d.dynamics.TimeStep; /** * @author eric * */ public class BuoyancyController extends Controller { /** * @param def */ protected BuoyancyController(BuoyancyControllerDef def) { super(def); normal = def.normal.clone(); offset = def.offset; density = def.density; velocity = def.velocity.clone(); linearDrag = def.linearDrag; angularDrag = def.angularDrag; useDensity = def.useDensity; useWorldGravity = def.useWorldGravity; gravity = def.gravity.clone(); } /** The outer surface normal */ public Vec2 normal = new Vec2(); /** The height of the fluid surface along the normal */ public float offset; /** The fluid density */ public float density; /** Fluid velocity, for drag calculations */ public Vec2 velocity = new Vec2(); /** Linear drag co-efficient */ public float linearDrag; /** Linear drag co-efficient */ public float angularDrag; /** If false, bodies are assumed to be uniformly dense, otherwise use the shapes densities */ public boolean useDensity; //False by default to prevent a gotcha /** If true, gravity is taken from the world instead of the gravity parameter. */ public boolean useWorldGravity; /** Gravity vector, if the world's gravity is not used */ public Vec2 gravity = new Vec2(); @Override public void step(final TimeStep step) { if(m_bodyList == null) return; if(useWorldGravity) { gravity = m_world.getGravity(); } for(ControllerEdge i=m_bodyList;i!=null;i=i.nextBody) { Body body = i.body; if(body.isSleeping()) { //Buoyancy force is just a function of position, //so unlike most forces, it is safe to ignore sleeping bodes continue; } Vec2 areac = new Vec2(0,0); Vec2 massc = new Vec2(0,0); float area = 0; float mass = 0; for(Shape shape=body.getShapeList();shape!=null;shape=shape.getNext()) { Vec2 sc = new Vec2(0,0); float sarea = shape.computeSubmergedArea(normal, offset, sc); area += sarea; areac.x += sarea * sc.x; areac.y += sarea * sc.y; float shapeDensity = 0; if(useDensity) { //TODO: Expose density publicly shapeDensity=shape.getDensity(); } else { shapeDensity = 1; } mass += sarea*shapeDensity; massc.x += sarea * sc.x * shapeDensity; massc.y += sarea * sc.y * shapeDensity; } areac.x/=area; areac.y/=area; //Vec2 localCentroid = XForm.mulTrans(body.getXForm(),areac); massc.x/=mass; massc.y/=mass; if(area<Settings.EPSILON) continue; //Buoyancy Vec2 buoyancyForce = gravity.mul(-density*area); body.applyForce(buoyancyForce,massc); //Linear drag Vec2 dragForce = body.getLinearVelocityFromWorldPoint(areac).sub(velocity); dragForce.mulLocal(-linearDrag*area); body.applyForce(dragForce,areac); //Angular drag //TODO: Something that makes more physical sense? body.applyTorque(-body.getInertia()/body.getMass()*area*body.getAngularVelocity()*angularDrag); } } @Override public void draw(DebugDraw debugDraw) { float r = 1000; Vec2 p1 = normal.mul(offset).addLocal(Vec2.cross(normal, r)); Vec2 p2 = normal.mul(offset).subLocal(Vec2.cross(normal, r)); Color3f color = new Color3f(0,0,255*0.8f); // debugDraw.drawSegment(p1, p2, color); Vec2[] vertices = new Vec2[] { p1, p2, p2.sub(normal.mul(r)), p1.sub(normal.mul(r))}; debugDraw.drawSolidPolygon(vertices, 4, color); } }