Предотвращение силового перемещения тел через другие тела с помощью MatterJS

14

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

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

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

К сожалению, это нарушает любые игры или симуляции в зависимости от перетаскивания. Я пробовал многочисленные решения, такие как нарушение ограничения мыши при столкновении или уменьшение жесткости ограничения, но ничего, что работало бы надежно.

Любые предложения приветствуются!

d13
источник
Я не понимаю формулировку, вызванную силой. Вы имеете в виду, что ваше затянувшееся тело должно пройти через любые другие тела?
Гродзи
Нет, это означает, что перетаскиваемое тело не должно проходить через любые другие тела.
д13
1
@ d13 Не могли бы вы добавить анимацию, показывающую проблему? Так как, кажется, есть некоторая путаница, основанная на формулировке ...
Призрак
2
@ Призрак добавил ...
d13
@ d13 Это проясняет ситуацию ..... это сложно
Ghost

Ответы:

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событие не обнаружено, если только мышь не находится внутри холста, когда это происходит, но это верно для любого Matter.jsmouseup обнаружения поэтому я не пытался это исправить.

Если скорость достаточно велика, 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, и, надеюсь, это обсуждение внесет свой вклад в реальное решение в будущем.
д13
@ d13 Спасибо, я согласен, что проблема, в конечном счете, заключается в основном коде, но я рад, что смог получить некоторое подобие решения (й)
Уильям Миллер
0

Я бы справился с этой функцией по-другому:

  • Нет «перетаскивания» (поэтому нет непрерывного выравнивания точки перетаскивания со смещением относительно перетаскиваемого объекта)
  • На mouseDown положение указателя мыши дает ориентированный вектор скорости для объекта, чтобы следовать
  • На mouseUp сбросьте ваш вектор скорости
  • Пусть симуляция материи сделает все остальное
Mosè Raguzzini
источник
1
Разве это не похоже на то, как matter.jsобрабатывать тела? от сюда «... как виртуальные пружины , которая крепится к мыши При перемещении ... пружина присоединенная [к телу] и вытягивается в направлении мышей ...».
Дух
Установка только скорости предотвращает перетаскивание внахлест, пружина проталкивает тело через других.
Мосе Рагуццини
Это может фактически указывать на решение. Если я правильно понимаю, это означает, что вы не используете встроенную в 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
Большое спасибо за отличную демонстрацию! Я на самом деле пытаюсь достичь противоположного эффекта: мне нужно предотвратить пересечение тел, когда одно перетаскивается в другое.
13
Извините, если я неправильно понял проблему. Можете ли вы уточнить, что вы имеете в виду, не давая телам пересекаться? Вы пытаетесь предотвратить перетаскивание других объектов при приложении силы?
Темур Чануквадзе
1
В этом случае это открытая проблема и не может быть сделано без жесткого кодирования для реализации CCD. Посмотрите: github.com/liabru/matter-js/issues/5
Темур Чануквадзе
0

Похоже, что это связано с проблемой 672 на их странице GitHub, которая, по-видимому, предполагает, что это происходит из-за отсутствия непрерывного обнаружения столкновений (CCD).

Была предпринята попытка исправить это, и код для этого можно найти здесь, но проблема все еще остается открытой, так что, похоже, вам, возможно, потребуется отредактировать движок, чтобы встроить в него CCD.

Mweya Ruider
источник
1
Спасибо за Ваш ответ! Я обдумал это, но я считаю, что это не проблема ПЗС, а проблема "Что происходит, когда непреодолимая сила встречает непреодолимое препятствие?" Почему-то мне нужно выяснить, как нейтрализовать силы, чтобы тела не пересекались.
13