import org.jbox2d.collision.CircleDef; import org.jbox2d.collision.Shape; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.World; import org.jbox2d.dynamics.contacts.ContactEdge; import org.jbox2d.dynamics.joints.MouseJoint; import org.jbox2d.dynamics.joints.MouseJointDef; import pulpcore.Input; import pulpcore.animation.Animation; import pulpcore.animation.BindFunction; import pulpcore.animation.Easing; import pulpcore.animation.Fixed; import pulpcore.animation.Timeline; import pulpcore.image.CoreGraphics; import pulpcore.image.CoreImage; import pulpcore.sprite.Sprite; import pulpfizz.physics.Actor; import pulpfizz.physics.BodyUtils; import pulpfizz.physics.CollisionUtils; import pulpfizz.physics.contact.ContactData; import pulpfizz.physics.contact.ContactEventListener; import pulpfizz.pulp.body.PhysicsLayer; public class Hand extends Sprite implements ContactEventListener { PhysicsLayer physics; CoreImage grab; CoreImage grabbing; CoreImage pointingLeft; CoreImage pointingRight; Shape sensorShape; Body sensorBody; Actor sensorActor; Body contactingBody; MouseJointDef jdef = new MouseJointDef(); MouseJoint joint; MouseJoint joint2; MouseJoint joint3; float downAngle; Fixed targetAngle; long downTime; private float origGamma; private float origBeta; public Hand(PhysicsLayer physics) { super(0, 0, 16, 16); this.physics = physics; // pixelSnapping.set(true); grab = CoreImage.load(GameScene.IMG_LOC + "grab.png"); grab.setHotspot(8, 8); grabbing = CoreImage.load(GameScene.IMG_LOC + "grabbing.png"); grabbing.setHotspot(8, 8); pointingLeft = CoreImage.load(GameScene.IMG_LOC + "pointingLeft.png"); pointingRight = CoreImage.load(GameScene.IMG_LOC + "pointingRight.png"); setAnchor(Sprite.CENTER); /* * Create the sensor that will test for object overlap. */ CircleDef cd = new CircleDef(); cd.radius = .3f; cd.isSensor = true; sensorBody = physics.getWorld().createBody(new BodyDef()); sensorBody.allowSleeping(false); sensorShape = sensorBody.createShape(cd); sensorActor = new Actor(); sensorActor.setName("Hand Sensor"); sensorActor.addBody(sensorBody); // physics.add(new Java2DBodySprite(sensorBody, physics)); physics.getWorld().getContactEventDispatcher().registerActorListener(sensorActor.getID(), this); targetAngle = new Fixed(); targetAngle.addListener(this); } protected Body queryForBody(Vec2 physPoint) { Body[] bodies = BodyUtils.getAllBodiesAt(physics.getWorld(), physPoint); Body goodChoice = null; for (Body b : bodies) { goodChoice = b; if (b != sensorBody) break; } return goodChoice; } private void grabPressed() { World w = physics.getWorld(); // assert joint == null; int x = Input.getMouseX(); int y = Input.getMouseY(); float physX = (float) physics.getPhysicsX(x, y); float physY = (float) physics.getPhysicsY(x, y); Vec2 physPoint = new Vec2(physX, physY); Body goodChoice = queryForBody(physPoint); if (goodChoice == null) return; final Body b = goodChoice; MouseJointDef jdef = new MouseJointDef(); jdef.body1 = w.getGroundBody(); jdef.body2 = b; jdef.target = physPoint; jdef.maxForce = 800 * b.getMass(); jdef.frequencyHz = 20f; jdef.dampingRatio = .9f; joint = (MouseJoint) w.createJoint(jdef); joint.m_localAnchor = b.getLocalPoint(physPoint); joint2 = (MouseJoint) w.createJoint(jdef); joint2.m_localAnchor = b.getLocalPoint(physPoint.add(getAngleVec(b))); joint3 = (MouseJoint) w.createJoint(jdef); joint3.m_localAnchor = b.getLocalPoint(physPoint.sub(getAngleVec(b))); angle.bindTo(new BindFunction() { final float origAngle = b.getAngle(); public Number f() { return -(b.getAngle() - origAngle); } }); origGamma = joint.m_gamma; origBeta = joint.m_beta; } private void grabReleased() { if (joint != null) { physics.getWorld().destroyJoint(joint); physics.getWorld().destroyJoint(joint2); physics.getWorld().destroyJoint(joint3); joint = null; joint2 = null; joint3 = null; angle.setBehavior(null); angle.animateTo(0, 500, Easing.STRONG_OUT); targetAngle.set(0); downAngle = 0; contactingBody = null; } } final static float D_THETA = 1f / 500f; final static float MAX_TURN = (float) Math.PI * .75f; final static float MAX_TURN_SPEED = 8f; float turnSpeed = 1f; private void turnLeft() { turn(1); } private void turnRight() { turn(-1); } private void turn(float dir) { turnSpeed *= 1.03f; if (turnSpeed > MAX_TURN_SPEED) turnSpeed = MAX_TURN_SPEED; if (!Input.isMouseDown() || joint == null) return; double curAngle = targetAngle.get(); if (targetAngle.getBehavior() != null) { targetAngle.update(1000); } double laterAngle = targetAngle.get(); targetAngle.set(curAngle); double newTarget = laterAngle + (dir) * Math.PI * 2f * D_THETA * turnSpeed; if (newTarget * dir > MAX_TURN) newTarget = MAX_TURN * dir; targetAngle.animateTo(newTarget, 400, Easing.REGULAR_OUT); } @Override protected void drawSprite(CoreGraphics g) { CoreImage img = getCorrectImage(); g.drawImage(img); } private CoreImage getCorrectImage() { if (System.currentTimeMillis() - downTime < 100 || joint != null) { // if we pressed recently, or the joint is active, return the hand. return grabbing; } else { return grab; } } private Vec2 getAngleVec(Body b) { Vec2 v = new Vec2((float) Math.cos(downAngle + targetAngle.get()), (float) Math.sin(downAngle + targetAngle.get())); /* * Make it a little more offset for heavier objects, but within a limit. */ float massMult = (float) Math.sqrt(b.getMass()); massMult = Math.min(4, massMult); return v.mulLocal(.2f * massMult); } @Override public void update(int elapsedTime) { super.update(elapsedTime); setDirty(true); // Always dirty... so the hand can rotate! // Grab the physics point for the mouse point. Vec2 mousePt = new Vec2((float) x.get(), (float) y.get()); Vec2 physPt = physics.canvasToPhysics(mousePt); // Update the sensor body. sensorBody.setXForm(physPt, 0); contactingBody = CollisionUtils.findFirstContact(sensorBody); // Process the left and right turn commands. if (Input.isDown(Input.KEY_LEFT) || Input.isDown(Input.KEY_A)) { turnLeft(); } else if (Input.isDown(Input.KEY_RIGHT) || Input.isDown(Input.KEY_D)) { turnRight(); } else { turnSpeed = 1; } // Update our position and target angle. targetAngle.update(elapsedTime); x.animateTo(Input.getMouseX(), 50, Easing.REGULAR_OUT); y.animateTo(Input.getMouseY(), 50, Easing.REGULAR_OUT); // Mouse clicks. if (Input.isMousePressed()) { downTime = System.currentTimeMillis(); grabPressed(); } else if (Input.isMouseReleased()) { grabReleased(); } // Low alpha if mouse is up and no objects underneath. if (joint == null) { if (contactingBody == null) { if (alpha.get() != 150) alpha.animateTo(150, 100); } else { if (alpha.get() != 255) alpha.animateTo(255, 20); } } else { if (alpha.get() != 255) alpha.animateTo(255, 20); } // Update the joints. if (joint != null) { World w = physics.getWorld(); Body b = joint.getBody2(); /* * Counteract gravity. */ b.applyForce(w.getGravity().mul(-b.getMass()), b.getWorldCenter()); /* * Update the target points. */ joint.setTarget(physPt); joint2.setTarget(physPt.add(getAngleVec(b))); joint3.setTarget(physPt.sub(getAngleVec(b))); /* * Figure out whether the joint should "be forceful" or "tread lightly". */ ContactEdge ce = b.m_contactList; boolean foundContact = false; while (ce != null) { if (ce.other != sensorBody) { foundContact = true; break; } ce = ce.next; } if (!foundContact) { beForceful(); } else if (treadLightlyWithBody(b)) { treadLightly(); } } } protected void beForceful() { // assert joint != null; joint.m_maxForce = 600 * joint.getBody2().getMass(); joint2.m_maxForce = 600 * joint2.getBody2().getMass() * getDangleMult(joint.getBody2()); joint3.m_maxForce = 600 * joint2.getBody2().getMass() * getDangleMult(joint.getBody2()); joint.m_gamma = origGamma; joint2.m_gamma = origGamma; joint3.m_gamma = origGamma; joint.m_beta = origBeta; joint2.m_beta = origBeta; joint3.m_beta = origBeta; } protected void treadLightly() { // assert joint != null; joint.m_maxForce = 100 * joint.getBody2().getMass(); joint2.m_maxForce = 100 * joint2.getBody2().getMass() * getDangleMult(joint.getBody2()); joint3.m_maxForce = 100 * joint2.getBody2().getMass() * getDangleMult(joint.getBody2()); // joint.m_gamma = origGamma * 2f; // joint2.m_gamma = origGamma * 2f; joint.m_beta = origBeta * .7f; joint2.m_beta = origBeta * .7f; joint3.m_beta = origBeta * .7f; } /** * Subclasses may want to override this to get per-body light treading. */ public boolean treadLightlyWithBody(Body b) { return true; } /** * A method to decide whether a certain body should be let to "dangle". * Currently does nothing, but subclasses can override. * * A return value of 0 will let the body dangle, and a return value of 1 * will use normal angular "strength". * * @param b * @return */ public float getDangleMult(Body b) { return 1; } public boolean dimEmptyHand() { return true; } public void trigger(ContactData c) { } }