web-dev-qa-db-ja.com

MatterJSで他のボディを介してボディを強制的にドラッグするのを防ぎます

私は物理ベースのゲームにMatterJを使用していて、マウスが他のボディを介してボディを強制的にドラッグすることを防ぐ問題の解決策を見つけていません。ボディを別のボディにドラッグすると、ドラッグされているボディは、他のボディに押し込まれ、他のボディを通過します。彼らが交差するのを防ぐ信頼できる方法を探しています。マウスでボディを選択し、別のボディを介してそれを強制しようとすることにより、MatterJSデモでこの効果を観察できます。これが典型的な例です:

enter image description here

https://brm.io/matter-js/demo/#staticFriction

残念ながら、これはドラッグアンドドロップに応じてゲームやシミュレーションを中断します。衝突が発生したときにマウスの制約を解除する、制約の剛性を下げるなど、さまざまな解決策を試しましたが、確実に機能するものはありませんでした。

どんな提案も歓迎します!

14
d13

ここでの最良の答えは、_Matter.Resolver_モジュールを大幅に見直して、ボディ間の物理的な競合を予測的に回避することです。それ以外のものは、特定の状況下で失敗することが保証されます。ここで言われているのは、実際には単なる部分的な解決策である2つの「解決策」です。それらの概要を以下に示します。


ソリューション1(更新)

このソリューションにはいくつかの利点があります。

  • ソリューション2よりも簡潔です
  • ソリューション2よりも小さい計算フットプリントを作成します
  • ソリューション2のようにドラッグ動作が中断されない
  • ソリューション2と非破壊的に組み合わせることができます

このアプローチの背後にあるアイデアは、「止められない力が動かないオブジェクトに出会うとき」力を止めることができるようにすることによって、起こることのパラドックスを解決することです。これは_Matter.Event_ beforeUpdateによって有効になり、各方向の絶対速度と衝撃(または実際には物理的な衝撃ではないpositionImpulse)を内に制限できますユーザー定義の境界。

_window.addEventListener('load', function() {
    var canvas = document.getElementById('world')
    var mouseNull = document.getElementById('mouseNull')
    var engine = Matter.Engine.create();
    var world = engine.world;
    var render = Matter.Render.create({    element: document.body, canvas: canvas,
                 engine: engine, options: { width: 800, height: 800,
                     background: 'transparent',showVelocity: true }});
    var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}), 
        size = 50, counter = -1;
     
    var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 
                                        0, 0, function(x, y) {
     return Matter.Bodies.rectangle(x, y, size * 2, size, {
         slop: 0, friction: 1,    frictionStatic: Infinity });
    });
    Matter.World.add(world, [ body, stack,
     Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
     Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
    ]);

    Matter.Events.on(engine, 'beforeUpdate', function(event) {
     counter += 0.014;
     if (counter < 0) { return; }
     var px = 400 + 100 * Math.sin(counter);
     Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
     Matter.Body.setPosition(body, { x: px, y: body.position.y });
     if (dragBody != null) {
        if (dragBody.velocity.x > 25.0) {
            Matter.Body.setVelocity(dragBody, {x: 25, y: dragBody.velocity.y });
        }
        if (dragBody.velocity.y > 25.0) {
            Matter.Body.setVelocity(dragBody, {x: dragBody.velocity.x, y: 25 });
        }
        if (dragBody.positionImpulse.x > 25.0) {
            dragBody.positionImpulse.x = 25.0;
        }
        if (dragBody.positionImpulse.y > 25.0) {
            dragBody.positionImpulse.y = 25.0;
        }
    }
    });

    var mouse = Matter.Mouse.create(render.canvas),
     mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
         constraint: { stiffness: 0.1, render: { visible: false }}});
     
    var dragBody = null


    Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
     dragBody = event.body;
    });
    
    Matter.World.add(world, mouseConstraint);
    render.mouse = mouse;
    Matter.Engine.run(engine);
    Matter.Render.run(render);
});_
_<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>_

この例では、velocitypositionImpulsexyを_25.0_の最大値に制限しています。結果を以下に示します

enter image description here

ご覧のように、ボディをドラッグするのは非常に暴力的で、ボディを互いに通過することはできません。これが、このアプローチを他のアプローチと区別するものです。他のほとんどの潜在的なソリューションは、ユーザーがドラッグするのに十分に暴力的である場合に失敗します。

この方法で遭遇した唯一の欠点は、非静的ボディを使用して、Resolverモジュールが失敗するポイントに十分な速度を与えるのに十分なほど強く、別の非静的ボディに当たることが可能であることです。衝突を検出し、2番目のボディが他のボディを通過できるようにします。 (静止摩擦の例では、必要な速度は_50.0_あたりです。これを成功させることができたのは1回だけであり、そのため、それを表すアニメーションはありません)。


解決策2

これは追加のソリューションですが、公正な警告です:簡単ではありません。

大まかに言えば、これが機能する方法は、ドラッグされているボディdragBodyが静的なボディと衝突しているかどうか、およびマウスがdragBodyを追従せずに移動しすぎていないかどうかを確認することです。マウスとdragBodyの間隔が大きくなりすぎたことを検出すると、 _Matter.js_ _mouse.mousemove_イベントリスナーを_mouse.element_から削除して置き換えます。別のマウスムーブ関数mousemove()を使用してください。この関数は、マウスが体の中心の特定の近接範囲内に戻ったかどうかをチェックします。残念ながら、組み込みのMatter.Mouse._getRelativeMousePosition()メソッドを適切に機能させることができなかったため、直接含める必要がありました(Javascriptで私より知識のある人がそれを理解する必要があります)。最後に、mouseupイベントが検出されると、通常のmousemoveリスナーに切り替わります。

_window.addEventListener('load', function() {
    var canvas = document.getElementById('world')
    var mouseNull = document.getElementById('mouseNull')
    var engine = Matter.Engine.create();
    var world = engine.world;
    var render = Matter.Render.create({ element: document.body, canvas: canvas,
                 engine: engine, options: { width: 800, height: 800,
                     background: 'transparent',showVelocity: true }});
    var body = Matter.Bodies.rectangle(400, 500, 200, 60, { isStatic: true}), 
        size = 50, counter = -1;
     
    var stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 
                                        0, 0, function(x, y) {
     return Matter.Bodies.rectangle(x, y, size * 2, size, {
         slop: 0.5, friction: 1,    frictionStatic: Infinity });
    });
    Matter.World.add(world, [ body, stack,
     Matter.Bodies.rectangle(400, 0, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true }),
     Matter.Bodies.rectangle(800, 300, 50, 600, { isStatic: true }),
     Matter.Bodies.rectangle(0, 300, 50, 600, { isStatic: true })
    ]);

    Matter.Events.on(engine, 'beforeUpdate', function(event) {
     counter += 0.014;
     if (counter < 0) { return; }
     var px = 400 + 100 * Math.sin(counter);
     Matter.Body.setVelocity(body, { x: px - body.position.x, y: 0 });
     Matter.Body.setPosition(body, { x: px, y: body.position.y });
    });

    var mouse = Matter.Mouse.create(render.canvas),
     mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse,
         constraint: { stiffness: 0.2, render: { visible: false }}});
     
    var dragBody, overshoot = 0.0, threshold = 50.0, loc, dloc, offset, 
    bodies = Matter.Composite.allBodies(world), moveOn = true;
    getMousePosition = function(event) {
     var element = mouse.element, pixelRatio = mouse.pixelRatio, 
        elementBounds = element.getBoundingClientRect(),
        rootNode = (document.documentElement || document.body.parentNode || 
                    document.body),
        scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset : 
                   rootNode.scrollLeft,
        scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : 
                   rootNode.scrollTop,
        touches = event.changedTouches, x, y;
     if (touches) {
         x = touches[0].pageX - elementBounds.left - scrollX;
         y = touches[0].pageY - elementBounds.top - scrollY;
     } else {
         x = event.pageX - elementBounds.left - scrollX;
         y = event.pageY - elementBounds.top - scrollY;
     }
     return { 
         x: x / (element.clientWidth / (element.width || element.clientWidth) *
            pixelRatio) * mouse.scale.x + mouse.offset.x,
         y: y / (element.clientHeight / (element.height || element.clientHeight) *
            pixelRatio) * mouse.scale.y + mouse.offset.y
     };
    };     
    mousemove = function() {
     loc = getMousePosition(event);
     dloc = dragBody.position;
     overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
     if (overshoot < threshold) {
         mouse.element.removeEventListener("mousemove", mousemove);
         mouse.element.addEventListener("mousemove", mouse.mousemove);
         moveOn = true;
     }
    }
    Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
     dragBody = event.body;
     loc = mouse.position;
     dloc = dragBody.position;
     offset = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5;
     Matter.Events.on(mouseConstraint, 'mousemove', function(event) {
         loc = mouse.position;
         dloc = dragBody.position;
         for (var i = 0; i < bodies.length; i++) {                      
             overshoot = ((loc.x - dloc.x)**2 + (loc.y - dloc.y)**2)**0.5 - offset;
             if (bodies[i] != dragBody && 
                 Matter.SAT.collides(bodies[i], dragBody).collided == true) {
                 if (overshoot > threshold) {
                     if (moveOn == true) {
                         mouse.element.removeEventListener("mousemove", mouse.mousemove);
                         mouse.element.addEventListener("mousemove", mousemove);
                         moveOn = false;
                     }
                 }
             }
         }
     });
    });

    Matter.Events.on(mouseConstraint, 'mouseup', function(event) {
     if (moveOn == false){
         mouse.element.removeEventListener("mousemove", mousemove);
         mouse.element.addEventListener("mousemove", mouse.mousemove);
         moveOn = true;
     }
    });
    Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
     overshoot = 0.0;
     Matter.Events.off(mouseConstraint, 'mousemove');
    });

    Matter.World.add(world, mouseConstraint);
    render.mouse = mouse;
    Matter.Engine.run(engine);
    Matter.Render.run(render);
});_
_<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.js"></script>_

イベントリスナーの切り替えスキームを適用した後、ボディはこのように動作します

enter image description here

私はこれをかなり完全にテストしましたが、すべての場合で動作することを保証できません。 mouseupイベントは、発生時にマウスがキャンバス内にない限り検出されないことにも注意してください。ただし、これはMatter.jsのmouseup検出に当てはまるため、実行しませんでした。修正してください。

速度が十分に大きい場合、Resolverは衝突の検出に失敗します。また、この物理的な衝突のフレーバーを予測的に防止できないため、ここに示すようにボディが通過できます。

enter image description here

これは、Solution 1と組み合わせることで解決できます。

ここで最後の注意点として、これを特定の相互作用のみに適用することが可能です(例:静的ボディと非静的ボディの間の相互作用)。これを行うには、

_if (bodies[i] != dragBody && Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}
_

へ(例:静的ボディ)

_if (bodies[i].isStatic == true && bodies[i] != dragBody && 
    Matter.SAT.collides(bodies[i], dragBody).collided == true) {
    //...
}
_

失敗したソリューション

将来のユーザーがこの質問に遭遇し、両方の解決策がユースケースには不十分であるとわかった場合、私が試した解決策の一部を以下に示します機能しません。してはいけないことのための種類のガイド。

  • _mouse.mouseup_を直接呼び出す:オブジェクトはすぐに削除されます。
  • Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse})を介した_mouse.mouseup_の呼び出し:_Engine.update_によってオーバーライドされ、動作は変更されません。
  • ドラッグされたオブジェクトを一時的に静的にする:非静的に戻るときにオブジェクトが削除されます(Matter.Body.setStatic(body, false)または_body.isStatic = false_を介して)。
  • 競合に近づくときにsetForceを介して_(0,0)_に力を設定すると、オブジェクトはまだ通過できますが、実際に機能するにはResolverに実装する必要があります。
  • _mouse.element_をsetElement()を介して、または_mouse.element_を直接変更して別のキャンバスに変更する:オブジェクトはすぐに削除されます。
  • オブジェクトを最後の「有効な」位置に戻す:引き続き通過を許可しますが、
  • collisionStartを介して動作を変更:一貫性のない衝突検出は、このメソッドでのパススルーを許可します

6
William Miller

これはGitHubページの issue 672 に関連しているようです。これは、継続的な衝突検出(CCD)がないために発生していることを示唆しているようです。

これを修正する試みが行われ、そのコードが見つかりました here しかし、問題はまだ開いているため、CCDを自分で構築するためにエンジンを編集する必要があるようです。

0
Mweya Ruider

ドラッグ時の衝突を制御するには、 衝突フィルターイベント を使用する必要があります。

デフォルトでボディを作成 衝突フィルタマスク0x0001。 catch startdragおよびenddragイベントを追加し、一時的に衝突を回避するために別の本体 衝突フィルターカテゴリ を設定します。

Matter.Events.on(mouseConstraint, 'startdrag', function(event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
});
Matter.Events.on(mouseConstraint, 'enddrag', function(event) {
     event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
});
window.addEventListener('load', function () {

  //Fetch our canvas
  var canvas = document.getElementById('world');

  //Setup Matter JS
  var engine = Matter.Engine.create();
  var world = engine.world;
  var render = Matter.Render.create({
                                      canvas: canvas,
                                      engine: engine,
                                      options: {
                                        width: 800,
                                        height: 800,
                                        background: 'transparent',
                                        wireframes: false,
                                        showAngleIndicator: false
                                      }
                                    });

  //Add a ball
  const size = 50;
  const stack = Matter.Composites.stack(350, 470 - 6 * size, 1, 6, 0, 0, (x, y) => {
    return Matter.Bodies.rectangle(x, y, size * 2, size, {
      collisionFilter: {
            mask: 0x0001,
      },
      slop: 0.5,
      friction: 1,
      frictionStatic: Infinity,
    });
  });

  Matter.World.add(engine.world, stack);

  //Add a floor
  var floor = Matter.Bodies.rectangle(250, 520, 500, 40, {
    isStatic: true, //An immovable object
    render: {
      visible: false
    }
  });
  Matter.World.add(world, floor);

  //Make interactive
  var mouseConstraint = Matter.MouseConstraint.create(engine, { //Create Constraint
    element: canvas,

    constraint: {
      render: {
        visible: false
      },
      stiffness: 0.8
    }
  });
  Matter.World.add(world, mouseConstraint);

  // add events to listen drag
  Matter.Events.on(mouseConstraint, 'startdrag', function (event) {
    event.body.collisionFilter.category = 0x0008; // move body to new category to avoid collision
  });
  Matter.Events.on(mouseConstraint, 'enddrag', function (event) {
    event.body.collisionFilter.category = 0x0001; // return body to default category to activate collision
  });

  //Start the engine
  Matter.Engine.run(engine);
  Matter.Render.run(render);

});
<canvas id="world"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.10.0/matter.min.js"></script>
0

私は別の方法で機能を管理したでしょう:

  • 「ドラッグ」なし(したがって、ドラッグポイントとオフセットされたオブジェクトとの連続的なドラッグポイントの整列はありません)
  • MouseDownで、マウスポインターの位置は、オブジェクトが従うべき方向付けられた速度ベクトルを与えます
  • MouseUpで速度ベクトルをリセットします
  • 物質シミュレーションに残りを任せる
0
Mosè Raguzzini