8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-10-13

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

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

スクリーンショット 2014-10-13 20.06.43.png

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

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

マウスドラッグで移動したときに
マウスジョイントの位置を動かすことでジョイントした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

今回はマウス位置に対して擬似的な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のジョイントは終わりで
次回からはより実践的な使い方をしていきます。
(キー入力での制御、衝突検知、センサー等)

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?