MOUSE JOINT

21
Jun
2012

The mouse joint is used to manipulate bodies with the mouse. It attempts to drive a point on a body towards the current position of the cursor. There is no restriction on rotation. But the way it's set up is quite complex and requires deeper understanding of Box2d. Therefore I'll just show you how to apply it for now and explain how it functions in the future.

1) Make sure the following codes are at the beginning.

var    b2Vec2 = Box2D.Common.Math.b2Vec2
,      b2BodyDef = Box2D.Dynamics.b2BodyDef
,      b2Body = Box2D.Dynamics.b2Body
,      b2FixtureDef = Box2D.Dynamics.b2FixtureDef
,      b2World = Box2D.Dynamics.b2World
,      b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
,      b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
,      b2RevoluteJointDef=Box2D.Dynamics.Joints.b2RevoluteJointDef
,      b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
,      b2DebugDraw = Box2D.Dynamics.b2DebugDraw
,      b2Fixture = Box2D.Dynamics.b2Fixture
,      b2AABB = Box2D.Collision.b2AABB;
Line 9 should be present.

2) Add the following codes after window.setInterval(update, 1000 / 60);
//mouse

var mouseX, mouseY, mousePVec, isMouseDown, selectedBody, mouseJoint;
var canvasPosition = getElementPosition(document.getElementById("canvas")); 

document.addEventListener("mousedown", function(e) {
   isMouseDown = true;
   handleMouseMove(e);
   document.addEventListener("mousemove", handleMouseMove, true);
}, true);

document.addEventListener("mouseup", function() {
   document.removeEventListener("mousemove", handleMouseMove, true);
   isMouseDown = false;
   mouseX = undefined;
   mouseY = undefined;
}, true);

function handleMouseMove(e) {
   mouseX = (e.clientX - canvasPosition.x) / 30;
   mouseY = (e.clientY - canvasPosition.y) / 30;
};

function getBodyAtMouse() {
   mousePVec = new b2Vec2(mouseX, mouseY);
   var aabb = new b2AABB();
   aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);
   aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);

   // Query the world for overlapping shapes.

   selectedBody = null;
   world.QueryAABB(getBodyCB, aabb);
   return selectedBody;
}

function getBodyCB(fixture) {
   if(fixture.GetBody().GetType() != 0) { //Static Bodies have type 0
      if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) {
         selectedBody = fixture.GetBody();
         return false;
      }
   }
   return true;
}

function getElementPosition(element) {
	var elem=element, tagname="", x=0, y=0;
    while((typeof(elem) == "object") && (typeof(elem.tagName) != "undefined")) {
	    y += elem.offsetTop;
        x += elem.offsetLeft;
        tagname = elem.tagName.toUpperCase();
		if(tagname == "BODY")
        	elem=0;
        if(typeof(elem) == "object") {
        	if(typeof(elem.offsetParent) == "object")
            	elem = elem.offsetParent;
        }
	}
    return {x: x, y: y};
}
Here what we're doing is that we first calculate the position of mouse i.e. x and y world coordinates in Box2d world (lines 20 and 21). Then we find out the body at that point in the world using World Querying by setting up a very small area as you can see in lines 27 and 28. (refer this tutorial for more info on World Querying). One thing worth noting is that since we don't want to move static bodies using our mouse, we do not return the body even when it is found as seen in line 38.

PS: If we want to use jQuery to get Canvas Position, we can replace line 4 with the following code:
var canvasPosition = {};
canvasPosition.x = $('canvas').offset().left;
canvasPosition.y = $('canvas').offset().top;
and get rid of the function getElementPosition starting from line 47.

3) Insert the following code inside the update() function. This effectively sets up a mouse joint between the body at mouse and a Ground Body (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) at the position of mouse.
if(isMouseDown && (!mouseJoint)) {
	var body = getBodyAtMouse();
    if(body) {
    	var md = new b2MouseJointDef();
        md.bodyA = world.GetGroundBody();
        md.bodyB = body;
        md.target.Set(mouseX, mouseY);
        md.collideConnected = true;
        md.maxForce = 300.0 * body.GetMass();
        mouseJoint = world.CreateJoint(md);
        body.SetAwake(true);
    }
}
if(mouseJoint) {
	if(isMouseDown) {
    	mouseJoint.SetTarget(new b2Vec2(mouseX, mouseY));
    } 
    else {
    	world.DestroyJoint(mouseJoint);
        mouseJoint = null;
	}
}
And now you're good to go.

Here is a demo.



By administrator at 03:52:11 PM 8 Comment(s)

Comments

Hello,

You don't need this line:

var canvasPosition = getElementPosition(document.getElementById("canvas"));

And you also don't need the entire getElementPosition function.
Instead, just change the handleMouseMove function so that it looks like this:

function handleMouseMove(e) {
mouseX = (e.pageX - document.querySelector("#canvas").offsetLeft) / 30;
mouseY = (e.pageY - document.querySelector("#canvas").offsetTop) / 30;
};

Great tutorials!

By d13 on 20 Nov, 2012 at 01:29:28 AM

That is what I have tried to do with the following lines:

var canvasPosition = {};
canvasPosition.x = $('canvas').offset().left;
canvasPosition.y = $('canvas').offset().top;

instead of

var canvasPosition = getElementPosition(document.getElementById("canvas")); 

This way we don't need the getElementPosition function. But your method is more suitable if we don't use jQuery.

By administrator on 20 Nov, 2012 at 05:40:27 AM

Hello, Suman. I got some problems here =/ QueryAABB doesn't work sometimes (in ~20% times) - it cant find body and returns nothing. Console.log(fixture) in getBodyCB() returns nothing too =/ What problem is here?

By Grammka on 14 Dec, 2012 at 05:45:55 AM

I don't know what might be causing the problem. In my case the only time console.log(fixture) doesn't return anything is when I click on an empty space and not on a body.

By administrator on 14 Dec, 2012 at 12:37:28 PM

I fix it! The problem was in $(function(){ … }); I put all content to js file and wrap with ready fn =/ I can't understand why it wasn't work, but now when scripts are going after body its work

By Grammka on 17 Dec, 2012 at 05:49:18 AM

All the scripts should be executed after DOM is ready. Glad you have worked it out.

By administrator on 17 Dec, 2012 at 09:51:59 AM

Nope) I mean this code:

$(function(){
   /* our code */
});

works with problems (i told about Joint - sometimes QueryAABB returns null when u click on body). But when I pulled my code from DOMready and appended it after body it work fine =/

By Grammka on 18 Dec, 2012 at 03:26:16 AM

@Grammka hmmm. i get it now.

By administrator on 18 Dec, 2012 at 09:20:40 AM

Add a Comment

Please enter the email address.Invalid format. (Won't be Displayed)
Notify me of followup comments
Enter the displayed Code: captcha