Запобігайте перетягуванню сил через інші тіла за допомогою MatterJS


14

Я використовую MatterJs для гри, заснованої на фізиці, і не знайшов рішення для проблеми, щоб запобігти перетягуванню тіл мишею через інші тіла. Якщо ви перетягнете тіло в інше тіло, тягнуче тіло може примусити себе до іншого тіла. Я шукаю надійний спосіб запобігти їх перетину. Ви можете спостерігати за цим ефектом у будь-якій демонстрації MatterJS, вибравши тіло за допомогою миші та намагаючись примусити його через інший орган. Ось типовий приклад:

введіть тут опис зображення

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

На жаль, це порушує будь-які ігри чи симуляції залежно від перетягування. Я спробував численні рішення, такі як порушення обмеження миші при зіткненні або зменшення жорсткості обмеження, але нічого не працює надійно.

Будь-які пропозиції ласкаво просимо!


Я не розумію примусового формулювання. Ви маєте на увазі, що ваше тягнуче тіло має пройти через будь-які інші тіла?
grodzi

Ні, це означає, що затягнутому тілу слід не допускати переходу будь-яких інших тіл.
d13

1
@ d13 Чи можете ви додати анімацію, що показує проблему? Оскільки, мабуть, існує деяка плутанина на основі формулювання ...
Привид

2
@Ghost додано ...
d13

@ d13 Це робить речі зрозумілішими ..... це хитро
привид

Відповіді:


6

Я думаю, що найкращою відповіддю тут буде суттєвий перегляд Matter.Resolverмодуля для здійснення передбачуваного уникнення фізичних конфліктів між будь-якими органами. Все, окрім цього, гарантовано зазнає краху за певних обставин. Сказане тут - це два "рішення", які насправді є лише частковими рішеннями. Вони викладені нижче.


Рішення 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і positionImpulseв, xі yдо максимальної величини 25.0. Результат показаний нижче

введіть тут опис зображення

Як бачите, можна перетягувати тіла досить сильно, і вони не пройдуть один через одного. Це те, що відрізняє цей підхід від інших: більшість інших потенційних рішень виходять з ладу, коли користувач достатньо жорстокий з їх перетягуванням.

Єдиним недоліком, з яким я стикався з цим методом, є те, що можна використовувати нестатичне тіло для удару іншого нестатичного тіла досить сильно, щоб надати йому достатню швидкість до тієї точки, коли Resolverмодуль не зможе виявити зіткнення і дозволити другий орган, що проходить через інші тіла. (У прикладі статичного тертя потрібна швидкість навколо 50.0, я встиг зробити це успішно лише один раз, і, отже, у мене немає анімації, що зображає це).


Рішення 2

Це додаткове рішення, хоча справедливе попередження: воно не є простим.

У широкому розумінні спосіб роботи полягає у тому, щоб перевірити, чи тіло, яке перетягується, dragBodyзіткнулося зі статичним корпусом і чи миша з тих пір пересунулася занадто далеко без dragBodyнаступних. Якщо він виявляє , що поділ між мишею і dragBodyстає занадто великим , що знімає слухач подій від і замінює його на іншу функцію MouseMove, . Ця функція перевіряє, чи повернулась миша в межах певної близькості до центру тіла. На жаль, я не міг змусити вбудований метод працювати належним чином, тому мені довелося включити його безпосередньо (хтось більш обізнаний, ніж я, у 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подія не виявляється, якщо миша не знаходиться в полотні, коли вона відбувається, але це справедливо для будь-якого mouseupвиявлення Matter.js, тому я не намагався це виправити.

Якщо швидкість буде достатньо великою, 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)через, setForceколи наближається до конфлікту: об'єкт все ще може пройти через, потрібно було б реалізувати, Resolverщоб реально працювати.
  • Змінення mouse.elementна інше полотно через setElement()або mouse.elementбезпосередньо за допомогою мутації : об’єкт видаляється негайно
  • Повернення об'єкта до останнього "дійсного" положення: все ще дозволяє пройти через,
  • Змініть поведінку через collisionStart: непослідовне виявлення зіткнення все ще дозволяє пройти через цей метод


Дуже дякую за ваш внесок! Я нагородив вас винагородою, бо, хоча ваше рішення не було ідеальним, воно, безумовно, вказує шлях вперед, і ви вкладаєте величезну кількість думок і часу в цю проблему - Дякую !! Зараз я впевнений, що ця проблема в кінцевому рахунку є розбіжністю в MatterJS, і, сподіваюся, ця дискусія сприятиме реальному вирішенню в майбутньому.
d13

@ d13 Дякую, я погоджуюсь, що проблема в кінцевому підсумку в базовому коді, але я рада, що я могла отримати певне вирішення (-ів) рішення
William Miller

0

Я б керував функцією іншим способом:

  • Немає "перетягування" (тому жодне безперервне вирівнювання точки перетягування зі зміщеним Vs перетягнутим об'єктом)
  • При натисканні миші позиціонування вказівника миші надає орієнтований вектор швидкості для наступного об'єкта
  • У режимі mouseUp скиньте вектор швидкості
  • Нехай справа імітації зробить все інше

1
Хіба це не так, як уже matter.jsсправляється з перетягуючими тілами? від сюди «... як віртуальні пружини , яка кріпиться до миші При переміщенні ... пружина приєднана [до тіла] і витягується в напрямку мишей ...».
Дух

Встановлення лише швидкості запобігає перетягуванню перекриття, шпигун примушує тіло через інших.
Mosè Raguzzini

Це насправді може вказувати на рішення. Якщо я правильно розумію, це означає не використовувати вбудований в MouseConstraint вбудований MatterJS, а встановлювати швидкість тіла вручну, виходячи з положення миші. Я не впевнений, як саме це буде реалізовано, тому, якщо хтось може розмістити подробиці про те, як вирівняти тіло до положення миші, не використовуючи setPosition або обмеження, будь ласка, зробіть це.
d13

@ d13 ви все ще будете покладатися на MatterJS, Resolverщоб вирішити, що робити щодо зіткнення тіл - переглянувши цей код справедливо, я думаю, що він все-таки вирішить дозволити пропуск через багато обставин ..... це може спрацювати, якщо ви також реалізований свою власну версію solveVelocityі , solvePositionале в цей момент ви все ще вручну робити те , що ви хочете MatterJS безпосередньо обробляти ....
Привид

0

Для управління зіткненням при перетягуванні потрібно використовувати фільтр зіткнення та події .

Створіть тіла з маскою фільтра зіткнення за замовчуванням 0x0001. Додайте улов 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>


1
Дякую вам за відмінну демонстрацію! Я насправді намагаюся досягти протилежного ефекту: мені потрібно не допустити перетину тіл, коли одне перетягується на інше.
d13

Вибачте, якщо я неправильно зрозумів проблему. Чи можете ви уточнити, що ви маєте на увазі, не даючи органам перетинатися? Ви намагаєтесь не допустити перетягування інших об'єктів при застосуванні сили?
Темур Чануквадзе

1
У цьому випадку це відкрита проблема, і її неможливо зробити без жорсткого кодування для реалізації CCD. Подивіться: github.com/liabru/matter-js/isissue/5
Темур Чануквадзе

0

Це, мабуть, пов’язане з випуском 672 на їхній сторінці GitHub, що, здається, дозволяє припустити, що це відбувається через відсутність безперервного виявлення зіткнень (CCD).

Спроба виправити це було зроблено, і код для цього можна знайти тут, але проблема все ще відкрита, тому, схоже, вам може знадобитися відредагувати двигун, щоб самостійно вбудувати в нього ПЗЗ.


1
Дякую за вашу відповідь! Я це врахував, але вважаю, що це не проблема ПЗЗ, а проблема "Що відбувається, коли сила, що не зупиняється, зустрічає незмінну перешкоду?" Якось мені потрібно розібратися, як нейтралізувати сили, щоб тіла не перетиналися.
d13
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.