Box2dWebでマウスで色々放り投げてみる

今回はbox2dwebでマウスジョイントをやります。

今回のスクリーンショットです。
円をマウスドラッグ＆ドロップで放り投げれます
正直実際に弄ってみないと感覚が掴めないと思います。

![スクリーンショット 2014-10-13 20.06.43.png](https://qiita-image-store.s3.amazonaws.com/0/55077/707f7e90-6baf-56ba-f200-635403ccae0f.png)

今回の全ソースコードです。

```js:test.html
<canvas id="canvas" width="600px" height="420px" style="background-color:#333333;"></canvas>

<script type="text/javascript" src="Box2dWeb-2.1.a.3.min.js"></script>
<script type="text/javascript">
      
    // Box2Dオブジェクトを取得
    var b2Vec2 = Box2D.Common.Math.b2Vec2 // 2Dベクトル
     ,  b2BodyDef = Box2D.Dynamics.b2BodyDef // Body定義
     ,  b2Body = Box2D.Dynamics.b2Body // Body
     ,  b2FixtureDef = Box2D.Dynamics.b2FixtureDef // Fixture定義
     ,  b2Fixture = Box2D.Dynamics.b2Fixture // Fixture
     ,  b2World = Box2D.Dynamics.b2World // 物理世界
     ,  b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape // 衝突オブジェクトの形状（ポリゴン）
     ,  b2CircleShape = Box2D.Collision.Shapes.b2CircleShape  // 衝突オブジェクトの形状（円）
     ,  b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef // マウスジョイント
     ,  b2AABB = Box2D.Collision.b2AABB // AABB(axis-aligned bounding box)
     ,  b2DebugDraw = Box2D.Dynamics.b2DebugDraw // デバッグ描画
 
 
    // 世界を作る
    var world = new b2World(new b2Vec2(0,10), true);
      
    ////////////////////////////////////

    var bodyDef = new b2BodyDef;
    bodyDef.type = b2Body.b2_dynamicBody;

    var fixDef = new b2FixtureDef;
    fixDef.density = 30;
    fixDef.friction = 10;
    fixDef.restitution = 0.1;
    fixDef.shape = new b2CircleShape(1);

    // 円
    bodyDef.position.Set(8,5);
    var wheel1 = world.CreateBody(bodyDef);
    wheel1.CreateFixture(fixDef);

    bodyDef.position.Set(11,5);
    var wheel2 = world.CreateBody(bodyDef);
    wheel2.CreateFixture(fixDef);
    
    // 地面
    var bodyDef = new b2BodyDef;
    bodyDef.type = b2Body.b2_staticBody;
    bodyDef.position.Set(0,12);
		
    var fd = new b2FixtureDef;
    fd.shape = new b2PolygonShape;
    fd.shape.SetAsBox(20,1);
		  
    var holder = world.CreateBody(bodyDef);
    holder.CreateFixture(fd);
    
    ////////////////////////////////////
 
 
    // デバッグ描画の設定      
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite ( document.getElementById ("canvas").getContext ("2d"));
    debugDraw.SetDrawScale(30);     //描画スケール
    debugDraw.SetFillAlpha(0.3);    //半透明値
    debugDraw.SetLineThickness(1.0);//線の太さ
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);// 何をデバッグ描画するか
    world.SetDebugDraw(debugDraw);
      
    window.setInterval(update,1000/60);
      
    // マウス
    var mouseX, mouseY, mousePVec, isMouseDown, selectedBody, mouseJoint;
    var canvasPosition = getElementPosition(document.getElementById("canvas"));

    function update() {
        
        // マウスが押されていて、マウスジョイントが作成されていない場合
        if(isMouseDown && (!mouseJoint)) {
		    var body = getBodyAtMouse();
            if(body) {
                // マウスジョイント作成
                var md = new b2MouseJointDef();
                md.bodyA = world.GetGroundBody(); // 物理世界
                md.bodyB = body; // ひも付け対象の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;
            }
        }

        world.Step(1 / 60, 10, 10); // 物理世界を更新する
        world.DrawDebugData(); // デバック描画
        world.ClearForces(); // 物理世界上の力をリセットする
        
    }
    		
    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;
    };
		
    // マウスの位置にあるBodyを取得する
    function getBodyAtMouse() {
        mousePVec = new b2Vec2(mouseX, mouseY);
        // 当たり判定用のAABB作成
        var aabb = new b2AABB();
        aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);// ボックスの左上座標
        aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);// ボックスの右下座標
		
        selectedBody = null;  
        // 指定のAABB領域内のBodyを探索する
        world.QueryAABB(getBodyCB, aabb);
        return selectedBody;
    }
		
    // AABB領域内のBodyを探索する関数
    function getBodyCB(fixture) {
        //console.log(fixture);
        // 静的Bodyじゃない場合（動的Bodyの場合）
        if(fixture.GetBody().GetType() != b2Body.b2_staticBody) {
            // マウスとBodyの当たり判定
            if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) {
				selectedBody = fixture.GetBody();// マウス位置にあるBodyを取得
                return false;// 探索を中止する
            }
        }
        return true; // AABB領域内の次のfixtureを探す
    }
		
		
    // ヘルパー関数
    // canvas位置取得用
    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};
    }
      
</script>
```

今回は、マウスジョイント用のb2MouseJointDefオブジェクトと
マウス当たり判定用のb2AABBオブジェクトを使います

```
    var b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef // マウスジョイント
    var b2AABB = Box2D.Collision.b2AABB // AABB(axis-aligned bounding box)
```

マウスジョイントの定義です。
自作のgetBodyAtMouse関数（後述）でマウス位置のBodyを取得します。
Bodyが存在するならばマウスジョイントを作成します。

```
    var body = getBodyAtMouse();
    if(body) {
        // マウスジョイント作成
        var md = new b2MouseJointDef();
        md.bodyA = world.GetGroundBody(); // 物理世界
        md.bodyB = body; // ひも付け対象のbody
        md.target.Set(mouseX, mouseY); // マウスジョイント位置
        md.collideConnected = true; // 二つのボディの衝突判定を行う
        md.maxForce = 300.0 * body.GetMass(); // マウスジョイントが引っ張る力
        mouseJoint = world.CreateJoint(md);
        body.SetAwake(true);// スリープ状態解除
   }
```

bodyAには普通は物理世界を指定します。
bodyBは引っ張りたいオブジェクトを指定します
collideConnectedは二つのボディ同士の衝突判定を行うフラグです。
デフォルトではfalseですが、今回のように片方のBodyが物理世界の場合、trueにしないと引っ張れません。
maxForceはマウスジョイントが引っ張る力を指定します。

SetAwake関数は
強制的にスリープ状態を解除しています

```
  body.SetAwake(true);// スリープ状態解除
```

Box2dではBodyが動かなくなると灰色になり
他のオブジェクトがぶつかったりして動くまで、物理計算処理の対象から除外されます（計算負荷軽減のためです）
この処理はスリープ状態を強制的に解除するための処理です。

![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/55077/d0900c4f-ee13-507b-82de-bbfcc0c80274.png)



マウスドラッグで移動したときに
マウスジョイントの位置を動かすことでジョイントしたBodyを引っ張ることが出来ます
マウスをドロップした（離した）ときにマウスジョイントを破壊することでBodyを放り投げることが出来ます

```
  if(mouseJoint) {
        // マウスドラッグしたとき
        if(isMouseDown) {                
             mouseJoint.SetTarget(new b2Vec2(mouseX, mouseY));
        } 
        // ドロップしたとき
        else {
             world.DestroyJoint(mouseJoint);
             mouseJoint = null;
        }
  }
```


マウス位置内にあるBodyを取得するために
AABBを作成して当たり判定を行います（getBodyAtMouse関数）

```
    // マウスの位置にあるBodyを取得する
    function getBodyAtMouse() {
        mousePVec = new b2Vec2(mouseX, mouseY);
        // 当たり判定用のAABB作成
        var aabb = new b2AABB();
        aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);
        aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);

        selectedBody = null;  
        // 指定のAABB領域内のBodyを探索する
        world.QueryAABB(getBodyCB, aabb);
        return selectedBody;
    }
```

AABB(axis-aligned bounding box)は軸に平行な包括ボックスで当たり判定などでよく使われます。

![world-querying-aabbs.png](https://qiita-image-store.s3.amazonaws.com/0/55077/ddd0a345-bf3a-2882-fc7c-8bcf1be69295.png)

今回はマウス位置に対して擬似的なAABBを作成してます。
ちなみに、軸に対して回転を許す包括ボックスにOBB(oriented bounding box)というものもあります。
こちらは向きも考慮するため、
包括したときの隙間がAABBよりも少なく精度が良いのですが、その分計算コストが高くなります。

aabbが作成できたら
QueryAABB関数で
AABB領域内のBodyの探索を行います
getBodyCB関数は自作の関数です。

```
    // 指定のAABB領域内のBodyを探索する
    world.QueryAABB(getBodyCB, aabb);
```

getBodyCB関数は次のように作成しています。
戻り値bool型の関数です。
引数はfixtureを指定します。

```
    // AABB領域内のBodyを探索する関数
    function getBodyCB(fixture) {
        //console.log(fixture);
        // 静的Bodyじゃない場合（動的Bodyの場合）
        if(fixture.GetBody().GetType() != b2Body.b2_staticBody) {
            // マウスとBodyの当たり判定
            if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) {
                selectedBody = fixture.GetBody();// マウス位置にあるBodyを取得
                return false;// 探索を中止する
            }
        }
        return true; // AABB領域内の次のfixtureを探す
    }
```

fixtureのGetBody関数でBodyを取得します。
静的Bodyはジョイントしても動かせないため、除外しています。
この関数は、戻り値trueでAABB領域内の次のfixtureを探し（再度getBodyCBがコールバックされます）、
マウス領域内にBodyがあれば取得し、戻り値falseで探索を中止します。

今回でbox2dwebのジョイントは終わりで
次回からはより実践的な使い方をしていきます。
（キー入力での制御、衝突検知、センサー等）