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


14

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

ここに画像の説明を入力してください

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

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

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


力ずくの言い回しがわかりません。あなたの引きずられた体は他の体を通り抜けるべきだという意味ですか?
grodzi

いいえ、ドラッグしたボディが他のボディを通過するのを防ぐ必要があることを意味します。
d13

1
@ d13問題を示すアニメーションを追加できますか?言葉遣いに基づいていくつかの混乱があるように見えるので...
ゴースト

2
@Ghostが追加されました...
d13

@ d13これでわかりやすくなります.....これはトリッキーです
Ghost

回答:


6

ここでの最良の答えは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>

例では、私は制限していますvelocityし、positionImpulsexyの最大の大きさに25.0。結果を以下に示します

ここに画像の説明を入力してください

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

この方法で遭遇した唯一の欠点は、非静的ボディを使用して、Resolverモジュールが衝突の検出に失敗し、他のボディを通過する2番目のボディ。(静止摩擦の例では、必要な速度はであり50.0、これを成功させることができたのは1回だけであり、そのため、それを示すアニメーションはありません)。


解決策2

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

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

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>

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

ここに画像の説明を入力してください

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

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

ここに画像の説明を入力してください

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


あなたの貢献に感謝します!あなたの解決策が完璧ではなかったとしても、それは間違いなく前進を示し、あなたはこの問題に莫大な量の考えと時間を費やしたので、賞金を授与しました-ありがとう!! この問題が最終的にMatterJSの機能ギャップであると確信しています。この議論が将来の真の解決策に役立つことを願っています。
d13

@ d13おかげで、問題は最終的には基礎となるコードにあることに同意しますが、解決策のいくつかの類似点を得ることができてうれしいです
William Miller

0

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

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

1
それはちょっとmatter.jsボディのドラッグをどのように処理するのですか?ここ から「...マウスに接続する仮想スプリングのように。ドラッグすると...スプリングが[ボディに]取り付けられ、マウスの方向に引っ張る...」
ゴースト

速度のみを設定すると、ドラッグによるオーバーラップが防止され、スピングはボディを他のボディに強制的に通過させます。
モーゼRaguzzini

これは実際に解決策を示している可能性があります。私が正しく理解していれば、それはMatterJSの組み込みのMouseConstraintを使用せず、マウスの位置に基づいて手動でボディの速度を設定することを意味します。これがどのように実装されるかは正確にはわかりませんが、setPositionや制約を使用せずに、本体をマウスの位置に合わせる方法の詳細を誰かが投稿できる場合は、実行してください。
d13 '20

@ d13あなたはまだResolver衝突物体について何をすべきかを決定するためにMatterJSに依存しているでしょう-そのコードをかなり調べたので、多くの状況でドラッグスルーを許可することを決定するだろうと思います.....また、独自のバージョンを実装solveVelocityしてsolvePosition....しかし、その時点であなたはまだ手動でMatterJSが直接処理するために欲しいものをやっている
ゴースト

0

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

デフォルトの衝突フィルターマスクで ボディを作成します0x0001。キャッチstartdragenddragイベントを追加し、別のボディ衝突フィルターカテゴリを設定して、一時的に衝突を回避します。

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>


1
素晴らしいデモをありがとう!私は実際には反対の効果を達成しようとしています。あるものが別の中にドラッグされたときにボディが交差しないようにする必要があります。
d13

問題を誤解した場合は申し訳ありません。体が交差しないようにすることで、あなたが何を意味するのかを明確にできますか?力が加えられたときに他のオブジェクトをドラッグしないようにしようとしていますか?
Temur Tchanukvadze

1
その場合、それは未解決の問題であり、CCDを実装するためのハードコーディングなしでは実行できません。見てください:github.com/liabru/matter-js/issues/5
Temur Tchanukvadze '17

0

これは、GitHubページの問題672に関連しているようです。これは、連続衝突検出(CCD)の欠如が原因で発生することを示唆しているようです。

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


1
ご回答有難うございます!私はこれを検討しましたが、CCDの問題ではなく、「止められない力が動かない障害物にぶつかるとどうなるのか」という問題だと思います。どういうわけか私は、体が交差するのを防ぐために力を無力化する方法を理解する必要があります。
d13
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.