Cegah tubuh menyeret paksa melalui badan lain dengan MatterJS

14

Saya menggunakan MatterJs untuk permainan berbasis fisika dan belum menemukan solusi untuk masalah mencegah tubuh diseret paksa oleh mouse melalui badan lain. Jika Anda menyeret satu tubuh ke tubuh lain, tubuh yang diseret dapat memaksa dirinya sendiri masuk dan melalui tubuh lainnya. Saya mencari cara yang dapat diandalkan untuk mencegah mereka berpotongan. Anda dapat mengamati efek ini di demo MatterJS dengan memilih tubuh dengan mouse, dan mencoba memaksanya melalui tubuh lain. Berikut ini adalah contoh khas:

masukkan deskripsi gambar di sini

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

Sayangnya ini memecah game atau simulasi tergantung pada drag-and-drop. Saya telah mencoba banyak solusi, seperti memecahkan batasan mouse saat tabrakan terjadi, atau mengurangi kekakuan kendala, tetapi tidak ada yang bisa diandalkan.

Ada saran!

d13
sumber
Saya tidak mengerti kata-kata yang terseret paksa. Apakah maksud Anda tubuh Anda yang diseret harus melalui badan lain?
grodzi
Tidak, itu berarti tubuh yang diseret harus dicegah agar tidak melewati benda lain.
d13
1
@ d13 Bisakah Anda menambahkan animasi yang menunjukkan masalah ini? Karena tampaknya ada beberapa kebingungan berdasarkan pada kata-kata ...
Ghost
2
@Ghost menambahkan ...
d13
@ d13 Itu membuat semuanya menjadi lebih jelas ..... ini adalah yang sulit
Ghost

Jawaban:

6

Saya pikir jawaban terbaik di sini adalah perbaikan yang signifikan pada Matter.Resolvermodul untuk menerapkan penghindaran prediktif dari konflik fisik antara setiap badan. Apa pun kekurangan itu dijamin gagal dalam keadaan tertentu. Yang dikatakan di sini adalah dua "solusi" yang, pada kenyataannya, hanyalah solusi parsial. Mereka diuraikan di bawah ini.


Solusi 1 (Pembaruan)

Solusi ini memiliki beberapa keunggulan:

  • Ini lebih ringkas daripada Solusi 2
  • Ini menciptakan jejak komputasi yang lebih kecil daripada Solusi 2
  • Perilaku seret tidak terputus seperti di Solusi 2
  • Ini dapat dikombinasikan secara non-destruktif dengan Solusi 2

Gagasan di balik pendekatan ini adalah untuk menyelesaikan paradoks dari apa yang terjadi " ketika suatu kekuatan yang tak terhentikan bertemu dengan objek yang tak tergoyahkan " dengan menjadikan kekuatan itu terhenti. Ini diaktifkan oleh Matter.Event beforeUpdate, yang memungkinkan kecepatan absolut dan impuls (atau lebih tepatnya positionImpulse, yang bukan benar-benar impuls fisik) di setiap arah untuk dibatasi ke dalam batas yang ditentukan pengguna.

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>

Dalam contoh ini saya membatasi velocitydan positionImpulsedalam xdan yuntuk besarnya maksimum 25.0. Hasilnya ditunjukkan di bawah ini

masukkan deskripsi gambar di sini

Seperti yang Anda lihat, sangat mungkin untuk melakukan kekerasan dalam menyeret tubuh dan mereka tidak akan melewati satu sama lain. Inilah yang membedakan pendekatan ini dari yang lain: sebagian besar solusi potensial gagal ketika pengguna cukup kasar dengan menyeret mereka.

Satu-satunya kekurangan yang saya temui dengan metode ini adalah bahwa adalah mungkin untuk menggunakan benda non-statis untuk mengenai benda non-statis yang cukup keras untuk memberikan kecepatan yang cukup ke titik di mana Resolvermodul akan gagal mendeteksi tabrakan dan memungkinkan tubuh kedua melewati tubuh lain. (Dalam contoh gesekan statis kecepatan yang diperlukan sekitar 50.0, saya hanya berhasil melakukan ini satu kali, dan akibatnya saya tidak memiliki animasi yang menggambarkannya).


Solusi 2

Ini adalah solusi tambahan, peringatan yang adil: itu tidak langsung.

Secara umum cara ini bekerja adalah untuk memeriksa apakah tubuh diseret dragBody,, telah bertabrakan dengan tubuh statis dan jika mouse telah bergerak terlalu jauh tanpa dragBodymengikuti. Jika mendeteksi bahwa pemisahan antara mouse dan dragBodytelah menjadi terlalu besar itu menghapus pendengar acara dari dan menggantikannya dengan fungsi mousemove yang berbeda, . Fungsi ini memeriksa apakah mouse telah kembali ke jarak tertentu dari pusat tubuh. Sayangnya saya tidak bisa mendapatkan metode built-in untuk bekerja dengan baik sehingga saya harus memasukkannya secara langsung (seseorang yang lebih berpengetahuan daripada saya di Javascript harus mencari yang keluar). Akhirnya, jika suatu peristiwa terdeteksi itu beralih kembali ke pendengar normal .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>

Setelah menerapkan skema switching pendengar acara, badan sekarang berperilaku lebih seperti ini

masukkan deskripsi gambar di sini

Saya telah menguji ini dengan cukup teliti, tetapi saya tidak dapat menjamin itu akan berhasil dalam setiap kasus. Ini juga mencatat bahwa mouseupacara tersebut tidak terdeteksi kecuali mouse berada di dalam kanvas ketika itu terjadi - tetapi ini berlaku untuk mouseupdeteksi Matter.js jadi saya tidak mencoba untuk memperbaikinya.

Jika kecepatannya cukup besar, Resolverakan gagal mendeteksi tabrakan, dan karena tidak ada pencegahan prediksi rasa konflik fisik ini, akan memungkinkan tubuh untuk melewatinya, seperti yang ditunjukkan di sini.

masukkan deskripsi gambar di sini

Ini dapat diatasi dengan menggabungkan dengan Solusi 1 .

Satu catatan terakhir di sini, adalah mungkin untuk menerapkan ini hanya pada interaksi tertentu (misalnya yang antara badan statis dan non-statis). Melakukannya dilakukan dengan mengubah

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

ke (untuk mis. badan statis)

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

Solusi yang gagal

Jika ada pengguna di masa depan menemukan pertanyaan ini dan menemukan kedua solusi tidak cukup untuk kasus penggunaannya, berikut adalah beberapa solusi yang saya coba yang tidak berhasil. Panduan untuk hal-hal yang tidak boleh dilakukan.

  • Memanggil mouse.mouseuplangsung: objek langsung dihapus.
  • Menelepon mouse.mouseupmelalui Event.trigger(mouseConstraint, 'mouseup', {mouse: mouse}): ditimpa oleh Engine.update, perilaku tidak berubah.
  • Membuat objek yang diseret untuk sementara statis: objek yang dihapus saat kembali ke non-statis (baik melalui Matter.Body.setStatic(body, false)atau body.isStatic = false).
  • Pengaturan kekuatan (0,0)melalui setForceketika mendekati konflik: objek masih bisa melewati, perlu diimplementasikan Resolveruntuk benar-benar bekerja.
  • Mengubah mouse.elementke kanvas yang berbeda melalui setElement()atau dengan bermutasi mouse.elementsecara langsung: objek segera dihapus.
  • Mengembalikan objek ke posisi 'valid' terakhir: masih memungkinkan untuk melewati,
  • Ubah perilaku melalui collisionStart: deteksi tabrakan yang tidak konsisten masih memungkinkan melewati metode ini

William Miller
sumber
Terima kasih banyak atas kontribusi Anda! Saya memberi Anda hadiah karena meskipun solusi Anda tidak sempurna, itu pasti menunjukkan jalan ke depan dan Anda menaruh sejumlah besar pemikiran dan waktu ke dalam masalah ini - Terima kasih !! Saya sekarang yakin bahwa masalah ini pada akhirnya adalah kesenjangan fitur di MatterJS, dan mudah-mudahan diskusi ini akan berkontribusi pada solusi nyata di masa depan.
d13
@ d13 Terima kasih, saya setuju bahwa masalahnya pada akhirnya ada dalam kode yang mendasarinya, tetapi saya senang saya bisa mendapatkan beberapa solusi (s)
William Miller
0

Saya akan mengatur fitur ini dengan cara lain:

  • Tidak ada "seret" (jadi tidak ada garis lurus antara dragpoint dengan objek seret offset Vs)
  • Pada mouseDown posisi pointer mouse memberikan vektor kecepatan berorientasi untuk mengikuti objek
  • Di mouse, atur ulang vektor kecepatan Anda
  • Biarkan simulasi materi mengerjakan sisanya
Mosè Raguzzini
sumber
1
Bukankah itu sudah cukup bagaimana matter.jsmenangani tubuh menyeret? dari sini "... seperti pegas virtual yang menempel pada mouse. Saat menyeret ... pegas terpasang [ke tubuh] dan menarik ke arah mouse ..."
Ghost
Pengaturan hanya kecepatan mencegah seret tumpang tindih, sping memaksa tubuh melalui orang lain.
Mosè Raguzzini
Ini mungkin sebenarnya menunjuk pada solusi. Jika saya mengerti dengan benar, itu berarti tidak menggunakan MatterJS bawaan MouseConstraint dan mengatur kecepatan tubuh secara manual berdasarkan posisi mouse. Saya tidak yakin persis bagaimana ini akan diterapkan, jadi jika ada yang bisa memposting rincian tentang bagaimana menyelaraskan tubuh ke posisi mouse, tanpa menggunakan setPosition atau kendala, silakan lakukan.
d13
@ d13 Anda masih akan mengandalkan MatterJS Resolveruntuk memutuskan apa yang harus dilakukan tentang bertabrakan - setelah memeriksa kode itu sedikit, saya berharap masih akan memutuskan untuk mengizinkan drag-through dalam banyak keadaan ..... mungkin bekerja jika Anda juga mengimplementasikan versi Anda sendiri solveVelocitydan solvePositiontetapi pada saat itu Anda masih melakukan secara manual apa yang Anda inginkan untuk ditangani MatterJS secara langsung ....
Ghost
0

Untuk mengontrol tabrakan saat diseret Anda perlu memanfaatkan tabrakan filter dan acara .

Buat badan dengan topeng filter tabrakan default 0x0001. Tambahkan tangkapan startdragdan enddragacara dan atur kategori filter tabrakan tubuh yang berbeda untuk menghindari tabrakan sementara.

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>

Temur Tchanukvadze
sumber
1
Terima kasih banyak untuk Anda demo luar biasa! Saya sebenarnya mencoba mencapai efek sebaliknya: Saya perlu mencegah tubuh berpotongan ketika seseorang diseret ke yang lain.
d13
Maaf Jika saya salah paham masalah ini. Bisakah Anda mengklarifikasi apa yang Anda maksud dengan mencegah tubuh bersinggungan? Apakah Anda mencoba untuk mencegah terseret melalui objek lain ketika kekuatan diterapkan?
Temur Tchanukvadze
1
Dalam hal ini masalah terbuka dan tidak dapat dilakukan tanpa hard-coding untuk mengimplementasikan CCD. Lihatlah: github.com/liabru/matter-js/issues/5
Temur Tchanukvadze
0

Ini tampaknya terkait dengan masalah 672 pada halaman GitHub mereka yang tampaknya menunjukkan bahwa ini terjadi karena kurangnya Continuous Collision Detection (CCD).

Upaya untuk memperbaiki ini telah dilakukan dan kode untuk itu dapat ditemukan di sini tetapi masalah masih terbuka sehingga sepertinya Anda mungkin perlu mengedit mesin untuk membangun CCD ke dalamnya sendiri.

Mweya Ruider
sumber
1
Terima kasih atas jawaban anda! Saya telah mempertimbangkan hal ini tetapi saya percaya ini bukan masalah CCD tetapi masalah "Apa yang terjadi ketika pasukan yang tak terhentikan bertemu dengan rintangan yang tidak bisa digerakkan?" Entah bagaimana saya perlu mencari cara untuk menetralisir kekuatan untuk mencegah tubuh bersinggungan.
d13